diff --git a/.github/workflows/checkPHP.yml b/.github/workflows/checkPHP.yml index eae9699..846f197 100644 --- a/.github/workflows/checkPHP.yml +++ b/.github/workflows/checkPHP.yml @@ -4,6 +4,9 @@ on: pull_request: branches: - beta + paths: + - '**/*.php' + workflow_dispatch: jobs: php-lint: diff --git a/.github/workflows/checkPHPCompat.yml b/.github/workflows/checkPHPCompat.yml index 6c9e618..e7705ad 100644 --- a/.github/workflows/checkPHPCompat.yml +++ b/.github/workflows/checkPHPCompat.yml @@ -4,6 +4,9 @@ on: pull_request: branches: - beta + paths: + - '**/*.php' + workflow_dispatch: jobs: php-compat: diff --git a/.github/workflows/checkPython.yml b/.github/workflows/checkPython.yml index 75c4024..381a50f 100644 --- a/.github/workflows/checkPython.yml +++ b/.github/workflows/checkPython.yml @@ -4,6 +4,9 @@ on: pull_request: branches: - beta + paths: + - 'resources/**/*.py' + workflow_dispatch: jobs: python-check: diff --git a/.github/workflows/js-check.yml b/.github/workflows/js-check.yml new file mode 100644 index 0000000..756c6a3 --- /dev/null +++ b/.github/workflows/js-check.yml @@ -0,0 +1,23 @@ +name: JavaScript Syntax Check + +on: + pull_request: + branches: + - beta + paths: + - '**/*.js' + workflow_dispatch: + +jobs: + js-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: '22' + + - name: Check JavaScript syntax + run: find . -name "*.js" ! -path "./node_modules/*" -exec node --check {} \; diff --git a/.gitignore b/.gitignore index 64a574d..90e00b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Plugin Configuration +data/filters/*.json + # IDE & Editors .vscode/* !.vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 600a248..1b576a9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -50,6 +50,7 @@ "lastscan", "lasttime", "lastused", + "logfilters", "maxrss", "molotov", "mycanal", diff --git a/core/ajax/tvremote.ajax.php b/core/ajax/tvremote.ajax.php index 8d2200b..5cad1f6 100644 --- a/core/ajax/tvremote.ajax.php +++ b/core/ajax/tvremote.ajax.php @@ -65,6 +65,16 @@ ajax::success(tvremote::sendBeginPairingAdb(init('mac'), init('host'))); } + if (init('action') == 'saveLogFilters') { + $data = init('logFiltersData'); + $filters = json_decode(is_string($data) ? $data : '[]', true); + if (!is_array($filters)) { + ajax::error(__('Format de données invalide', __FILE__)); + } + tvremote::saveLogFilters($filters); + ajax::success(); + } + throw new Exception(__('Aucune méthode correspondante à', __FILE__) . ' : ' . init('action')); /* * *********Catch exception*************** */ } catch (Exception $e) { diff --git a/core/class/tvremote.class.php b/core/class/tvremote.class.php index d9b0298..18be928 100644 --- a/core/class/tvremote.class.php +++ b/core/class/tvremote.class.php @@ -138,6 +138,7 @@ public static function deamon_start() { $cmd = self::PYTHON3_PATH . " {$path}/tvremoted.py"; $cmd .= ' --loglevel ' . log::convertLogLevel(log::getLogLevel(__CLASS__)); $cmd .= ' --tvloglevel ' . config::byKey('tvLogLevel', __CLASS__, 'daemon'); + $cmd .= ' --logfiltersenabled ' . (config::byKey('logFiltersEnabled', __CLASS__, '0') ? '1' : '0'); $cmd .= ' --pluginversion ' . config::byKey('pluginVersion', __CLASS__, '0.0.0'); $cmd .= ' --socketport ' . config::byKey('socketport', __CLASS__, '55112'); $cmd .= ' --cyclefactor ' . config::byKey('cyclefactor', __CLASS__, '1.0'); @@ -559,6 +560,28 @@ public function disableADBToDaemon() { } } + public static function createLogFiltersFile() { + $path = dirname(__FILE__) . '/../../data/filters/logfilters.json'; + if (!file_exists($path)) { + file_put_contents($path, '[]'); + } + } + + public static function getLogFiltersFileContent() { + $path = dirname(__FILE__) . '/../../data/filters/logfilters.json'; + if (!file_exists($path)) { + return array(); + } + return json_decode(file_get_contents($path), true) ?: array(); + } + + public static function saveLogFilters($filters) { + $path = dirname(__FILE__) . '/../../data/filters/logfilters.json'; + if (file_put_contents($path, json_encode($filters, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)) === false) { + throw new Exception(__('Impossible d\'enregistrer le fichier logfilters.json', __FILE__)); + } + } + public static function pairingException($_data) { if (!isset($_data['pairing_mac'])) { log::add('tvremote', 'error', '[PAIRING][EXCEPTION] Information manquante (MAC) pour mettre à jour l\'équipement'); diff --git a/core/i18n/de_DE.json b/core/i18n/de_DE.json index 28d1871..cba1cf5 100644 --- a/core/i18n/de_DE.json +++ b/core/i18n/de_DE.json @@ -1,7 +1,8 @@ { "plugins\/tvremote\/core\/ajax\/tvremote.ajax.php": { "401 - Accès non autorisé": "401 - Unberechtigter Zugriff", - "Aucune méthode correspondante à": "Keine passende Methode zu" + "Aucune méthode correspondante à": "Keine passende Methode zu", + "Format de données invalide": "Ungültiges Datenformat" }, "plugins\/tvremote\/core\/class\/tvremote.class.php": { "0": "0", @@ -36,6 +37,7 @@ "HDMI 3": "HDMI 3", "HDMI 4": "HDMI 4", "Home": "Home", + "Impossible d\\'enregistrer le fichier logfilters.json": "Die Datei „logfilters.json“ kann nicht gespeichert werden", "Impossible de lancer le démon, vérifiez le log": "Der Dämon kann nicht gestartet werden, überprüfen Sie das Log", "Info": "Info", "Input": "Input", @@ -84,6 +86,13 @@ "plugins\/tvremote\/core\/php\/jeetvremote.php": { "Vous n\\'etes pas autorisé à effectuer cette action": "Sie sind nicht berechtigt, diese Aktion durchzuführen" }, + "plugins\/tvremote\/desktop\/js\/logfilters.tvremote.js": { + "Erreur lors de la sauvegarde": "Fehler beim Speichern", + "Ex: Cannot connect (device may be offline)": "Beispiel: Verbindung nicht möglich (Gerät ist möglicherweise offline)", + "Filtres sauvegardés": "Gespeicherte Filter", + "Redémarrez le démon pour appliquer les changements": "Starten Sie den Daemon neu, um die Änderungen zu übernehmen", + "Supprimer": "Löschen" + }, "plugins\/tvremote\/desktop\/js\/tvremote.js": { "ADB Shell": "ADB-Shell", "Afficher": "Anzeigen", @@ -116,6 +125,18 @@ "Échec de l'appairage ADB pour": "Fehler beim ADB-Pairing für", "Échec de l'appairage TVRemote pour": "Fehler beim Koppeln von TVRemote für" }, + "plugins\/tvremote\/desktop\/modal\/logfilters.tvremote.php": { + "401 - Accès non autorisé": "401 - Unberechtigter Zugriff", + "Actif": "Aktiv", + "Ajouter": "Hinzufügen", + "Définissez les patterns à intercepter et le niveau de log à appliquer. Cochez\/décochez pour activer\/désactiver une règle sans la perdre.": "Legen Sie die abzufangenden Muster und die anzuwendende Protokollierungsstufe fest. Aktivieren oder deaktivieren Sie das Kontrollkästchen, um eine Regel zu aktivieren bzw. zu deaktivieren, ohne sie zu verlieren.", + "Niveau cible": "Zielniveau", + "Pattern (sous-chaîne du message de log)": "Muster (Teilzeichenfolge der Protokollmeldung)", + "Sauvegarder": "Speichern", + "Suggestions :": "Vorschläge:", + "Tout activer": "Alles aktivieren", + "Tout désactiver": "Alles deaktivieren" + }, "plugins\/tvremote\/desktop\/php\/tvremote.php": { "401 - Accès non autorisé": "401 - Unberechtigter Zugriff", "ADB": "ADB", @@ -196,6 +217,8 @@ "Vous disposez de 5 minutes pour entrer le code après avoir cliqué sur Appairer.": "Sie haben nach dem Klicken auf „Koppeln“ 5 Minuten Zeit, um den Code einzugeben." }, "plugins\/tvremote\/plugin_info\/configuration.php": { + "Active la personnalisation des niveaux de log. Les filtres sont définis dans la fenêtre de gestion dédiée.": "Aktiviert die Anpassung der Protokollstufen. Die Filter werden im entsprechenden Verwaltungsfenster definiert.", + "Activer les filtres de logs": "Log-Filter aktivieren", "Certificat ADB": "ADB-Zertifikat", "Certificat TVRemote": "TVRemote-Zertifikat", "Cocher cette case désactivera les messages de mise à jour du plugin dans le centre de message": "Wenn Sie dieses Kästchen ankreuzen, werden die Update-Meldungen des Plugins im Nachrichtencenter deaktiviert.", @@ -206,10 +229,12 @@ "Désactiver les messages de MàJ": "Update-Meldungen deaktivieren", "Error": "Fehler", "Facteur multiplicateur des cycles du démon (Défaut = x1)": "Multiplikationsfaktor der Dämonenzyklen (Standard = x1)", + "Filtres de logs": "Protokollfilter", "Force la réinitialisation de PyEnv": "Erzwingt das Zurücksetzen von PyEnv", "Force la réinitialisation de Venv": "Erzwingt das Zurücksetzen von Venv", "Force les mises à jour Systèmes": "Erzwingt Aktualisierungen Systeme", "Fréquence des cycles": "Häufigkeit der Zyklen", + "Gérer les filtres": "Filter verwalten", "Identique au démon (défaut)": "Identisch mit dem Daemon (Standard)", "Info": "Info", "Le démon devra être redémarré après cette action": "Der Daemon muss nach dieser Aktion neu gestartet werden.", diff --git a/core/i18n/en_US.json b/core/i18n/en_US.json index 979125c..8ef8d12 100644 --- a/core/i18n/en_US.json +++ b/core/i18n/en_US.json @@ -1,7 +1,8 @@ { "plugins\/tvremote\/core\/ajax\/tvremote.ajax.php": { "401 - Accès non autorisé": "401 - Unauthorized access", - "Aucune méthode correspondante à": "No corresponding method for" + "Aucune méthode correspondante à": "No corresponding method for", + "Format de données invalide": "Invalid data format" }, "plugins\/tvremote\/core\/class\/tvremote.class.php": { "0": "0", @@ -36,6 +37,7 @@ "HDMI 3": "HDMI 3", "HDMI 4": "HDMI 4", "Home": "Home", + "Impossible d\\'enregistrer le fichier logfilters.json": "Unable to save the logfilters.json file", "Impossible de lancer le démon, vérifiez le log": "Unable to start daemon, check log", "Info": "Info", "Input": "Input", @@ -84,6 +86,13 @@ "plugins\/tvremote\/core\/php\/jeetvremote.php": { "Vous n\\'etes pas autorisé à effectuer cette action": "You are not authorized to perform this action" }, + "plugins\/tvremote\/desktop\/js\/logfilters.tvremote.js": { + "Erreur lors de la sauvegarde": "Error while saving", + "Ex: Cannot connect (device may be offline)": "Example: Cannot connect (device may be offline)", + "Filtres sauvegardés": "Saved filters", + "Redémarrez le démon pour appliquer les changements": "Restart the daemon to apply the changes", + "Supprimer": "Delete" + }, "plugins\/tvremote\/desktop\/js\/tvremote.js": { "ADB Shell": "ADB Shell", "Afficher": "Display", @@ -116,6 +125,18 @@ "Échec de l'appairage ADB pour": "ADB pairing failed for", "Échec de l'appairage TVRemote pour": "TVRemote pairing failed for" }, + "plugins\/tvremote\/desktop\/modal\/logfilters.tvremote.php": { + "401 - Accès non autorisé": "401 - Unauthorized access", + "Actif": "Active", + "Ajouter": "Add", + "Définissez les patterns à intercepter et le niveau de log à appliquer. Cochez\/décochez pour activer\/désactiver une règle sans la perdre.": "Define the patterns to intercept and the logging level to apply. Check or uncheck the box to enable or disable a rule without losing it.", + "Niveau cible": "Target level", + "Pattern (sous-chaîne du message de log)": "Pattern (substring of the log message)", + "Sauvegarder": "Save", + "Suggestions :": "Suggestions:", + "Tout activer": "Turn everything on", + "Tout désactiver": "Turn everything off" + }, "plugins\/tvremote\/desktop\/php\/tvremote.php": { "401 - Accès non autorisé": "401 - Unauthorized access", "ADB": "ADB", @@ -196,6 +217,8 @@ "Vous disposez de 5 minutes pour entrer le code après avoir cliqué sur Appairer.": "You have 5 minutes to enter the code after clicking Pair." }, "plugins\/tvremote\/plugin_info\/configuration.php": { + "Active la personnalisation des niveaux de log. Les filtres sont définis dans la fenêtre de gestion dédiée.": "Active customization of log levels. Filters are defined in the dedicated management window.", + "Activer les filtres de logs": "Enable log filters", "Certificat ADB": "ADB certificate", "Certificat TVRemote": "TVRemote certificate", "Cocher cette case désactivera les messages de mise à jour du plugin dans le centre de message": "Checking this box will disable plugin update messages in the message center.", @@ -206,10 +229,12 @@ "Désactiver les messages de MàJ": "Disable update messages", "Error": "Error", "Facteur multiplicateur des cycles du démon (Défaut = x1)": "Demon cycle multiplication factor (Default = x1)", + "Filtres de logs": "Log filters", "Force la réinitialisation de PyEnv": "Force PyEnv reset", "Force la réinitialisation de Venv": "Force reset Venv", "Force les mises à jour Systèmes": "Force system updates", "Fréquence des cycles": "Cycle frequency", + "Gérer les filtres": "Manage filters", "Identique au démon (défaut)": "Same as the (default) daemon", "Info": "Info", "Le démon devra être redémarré après cette action": "The daemon will need to be restarted after this action.", diff --git a/core/i18n/es_ES.json b/core/i18n/es_ES.json index 81794e2..82f6449 100644 --- a/core/i18n/es_ES.json +++ b/core/i18n/es_ES.json @@ -1,7 +1,8 @@ { "plugins\/tvremote\/core\/ajax\/tvremote.ajax.php": { "401 - Accès non autorisé": "401 - Acceso no autorizado", - "Aucune méthode correspondante à": "Ningún método correspondiente a" + "Aucune méthode correspondante à": "Ningún método correspondiente a", + "Format de données invalide": "Formato de datos no válido" }, "plugins\/tvremote\/core\/class\/tvremote.class.php": { "0": "0", @@ -36,6 +37,7 @@ "HDMI 3": "HDMI 3", "HDMI 4": "HDMI 4", "Home": "Inicio", + "Impossible d\\'enregistrer le fichier logfilters.json": "No se puede guardar el archivo logfilters.json", "Impossible de lancer le démon, vérifiez le log": "No se puede iniciar el demonio, compruebe el registro", "Info": "Información", "Input": "Entrada", @@ -84,6 +86,13 @@ "plugins\/tvremote\/core\/php\/jeetvremote.php": { "Vous n\\'etes pas autorisé à effectuer cette action": "No está autorizado a realizar esta acción" }, + "plugins\/tvremote\/desktop\/js\/logfilters.tvremote.js": { + "Erreur lors de la sauvegarde": "Error al guardar", + "Ex: Cannot connect (device may be offline)": "Ej.: No se puede conectar (es posible que el dispositivo esté desconectado)", + "Filtres sauvegardés": "Filtros guardados", + "Redémarrez le démon pour appliquer les changements": "Reinicia el demonio para aplicar los cambios", + "Supprimer": "Borrar" + }, "plugins\/tvremote\/desktop\/js\/tvremote.js": { "ADB Shell": "ADB Shell", "Afficher": "Ver", @@ -116,6 +125,18 @@ "Échec de l'appairage ADB pour": "Error al emparejar ADB para", "Échec de l'appairage TVRemote pour": "Error al emparejar TVRemote para" }, + "plugins\/tvremote\/desktop\/modal\/logfilters.tvremote.php": { + "401 - Accès non autorisé": "401 - Acceso no autorizado", + "Actif": "Activo", + "Ajouter": "Añadir", + "Définissez les patterns à intercepter et le niveau de log à appliquer. Cochez\/décochez pour activer\/désactiver une règle sans la perdre.": "Define los patrones que se deben interceptar y el nivel de registro que se debe aplicar. Marca o desmarca la casilla para activar o desactivar una regla sin perderla.", + "Niveau cible": "Nivel objetivo", + "Pattern (sous-chaîne du message de log)": "Patrón (subcadena del mensaje de registro)", + "Sauvegarder": "Guardar", + "Suggestions :": "Sugerencias:", + "Tout activer": "Activar todo", + "Tout désactiver": "Desactivarlo todo" + }, "plugins\/tvremote\/desktop\/php\/tvremote.php": { "401 - Accès non autorisé": "401 - Acceso no autorizado", "ADB": "ADB", @@ -196,6 +217,8 @@ "Vous disposez de 5 minutes pour entrer le code après avoir cliqué sur Appairer.": "Tienes 5 minutos para introducir el código después de hacer clic en Emparejar." }, "plugins\/tvremote\/plugin_info\/configuration.php": { + "Active la personnalisation des niveaux de log. Les filtres sont définis dans la fenêtre de gestion dédiée.": "Activa la personalización de los niveles de registro. Los filtros se definen en la ventana de gestión correspondiente.", + "Activer les filtres de logs": "Activar los filtros de registros", "Certificat ADB": "Certificado ADB", "Certificat TVRemote": "Certificado TVRemote", "Cocher cette case désactivera les messages de mise à jour du plugin dans le centre de message": "Al marcar esta casilla, se desactivarán los mensajes de actualización del complemento en el centro de mensajes.", @@ -206,10 +229,12 @@ "Désactiver les messages de MàJ": "Desactivar los mensajes de actualización", "Error": "Error", "Facteur multiplicateur des cycles du démon (Défaut = x1)": "Factor de multiplicación del ciclo Demon (Por defecto = x1)", + "Filtres de logs": "Filtros de registros", "Force la réinitialisation de PyEnv": "Forzar reinicio de PyEnv", "Force la réinitialisation de Venv": "Forzar reinicio de Venv", "Force les mises à jour Systèmes": "Forzar actualizaciones del sistema", "Fréquence des cycles": "Frecuencia de ciclo", + "Gérer les filtres": "Gestionar los filtros", "Identique au démon (défaut)": "Igual que el demonio (por defecto)", "Info": "Información", "Le démon devra être redémarré après cette action": "El demonio deberá reiniciarse después de esta acción.", diff --git a/core/i18n/it_IT.json b/core/i18n/it_IT.json index 2e04eaa..74e7e39 100644 --- a/core/i18n/it_IT.json +++ b/core/i18n/it_IT.json @@ -1,7 +1,8 @@ { "plugins\/tvremote\/core\/ajax\/tvremote.ajax.php": { "401 - Accès non autorisé": "401 - Accesso non autorizzato", - "Aucune méthode correspondante à": "Nessun metodo corrispondente a" + "Aucune méthode correspondante à": "Nessun metodo corrispondente a", + "Format de données invalide": "Formato dati non valido" }, "plugins\/tvremote\/core\/class\/tvremote.class.php": { "0": "0", @@ -36,6 +37,7 @@ "HDMI 3": "HDMI 3", "HDMI 4": "HDMI 4", "Home": "Casa", + "Impossible d\\'enregistrer le fichier logfilters.json": "Impossibile salvare il file logfilters.json", "Impossible de lancer le démon, vérifiez le log": "Impossibile avviare il demone, controllare il registro", "Info": "Info", "Input": "Ingresso", @@ -84,6 +86,13 @@ "plugins\/tvremote\/core\/php\/jeetvremote.php": { "Vous n\\'etes pas autorisé à effectuer cette action": "Non siete autorizzati ad eseguire questa azione" }, + "plugins\/tvremote\/desktop\/js\/logfilters.tvremote.js": { + "Erreur lors de la sauvegarde": "Errore durante il salvataggio", + "Ex: Cannot connect (device may be offline)": "Es.: Impossibile connettersi (il dispositivo potrebbe essere offline)", + "Filtres sauvegardés": "Filtri salvati", + "Redémarrez le démon pour appliquer les changements": "Riavviare il demone per applicare le modifiche", + "Supprimer": "Cancellare" + }, "plugins\/tvremote\/desktop\/js\/tvremote.js": { "ADB Shell": "ADB Shell", "Afficher": "Vista", @@ -116,6 +125,18 @@ "Échec de l'appairage ADB pour": "Errore nell'accoppiamento ADB per", "Échec de l'appairage TVRemote pour": "Errore nell'accoppiamento TVRemote per" }, + "plugins\/tvremote\/desktop\/modal\/logfilters.tvremote.php": { + "401 - Accès non autorisé": "401 - Accesso non autorizzato", + "Actif": "Attivo", + "Ajouter": "Aggiungi", + "Définissez les patterns à intercepter et le niveau de log à appliquer. Cochez\/décochez pour activer\/désactiver une règle sans la perdre.": "Definisci i modelli da intercettare e il livello di registrazione da applicare. Seleziona\/deseleziona per attivare\/disattivare una regola senza perderla.", + "Niveau cible": "Livello previsto", + "Pattern (sous-chaîne du message de log)": "Pattern (sottostringa del messaggio di log)", + "Sauvegarder": "Risparmiare", + "Suggestions :": "Suggerimenti:", + "Tout activer": "Attiva tutto", + "Tout désactiver": "Disattiva tutto" + }, "plugins\/tvremote\/desktop\/php\/tvremote.php": { "401 - Accès non autorisé": "401 - Accesso non autorizzato", "ADB": "ADB", @@ -196,6 +217,8 @@ "Vous disposez de 5 minutes pour entrer le code après avoir cliqué sur Appairer.": "Hai 5 minuti di tempo per inserire il codice dopo aver cliccato su Accoppia." }, "plugins\/tvremote\/plugin_info\/configuration.php": { + "Active la personnalisation des niveaux de log. Les filtres sont définis dans la fenêtre de gestion dédiée.": "Attiva la personalizzazione dei livelli di registrazione. I filtri vengono definiti nella finestra di gestione dedicata.", + "Activer les filtres de logs": "Attivare i filtri dei log", "Certificat ADB": "Certificato ADB", "Certificat TVRemote": "Certificato TVRemote", "Cocher cette case désactivera les messages de mise à jour du plugin dans le centre de message": "Selezionando questa casella, i messaggi di aggiornamento del plugin nel centro messaggi saranno disattivati.", @@ -206,10 +229,12 @@ "Désactiver les messages de MàJ": "Disattivare i messaggi di aggiornamento", "Error": "Errore", "Facteur multiplicateur des cycles du démon (Défaut = x1)": "Fattore di moltiplicazione dei cicli demonici (default = x1)", + "Filtres de logs": "Filtri dei log", "Force la réinitialisation de PyEnv": "Forza il reset di PyEnv", "Force la réinitialisation de Venv": "Ripristino forzato di Venv", "Force les mises à jour Systèmes": "Forzare gli aggiornamenti del sistema", "Fréquence des cycles": "Frequenza del ciclo", + "Gérer les filtres": "Gestisci i filtri", "Identique au démon (défaut)": "Identico al demone (predefinito)", "Info": "Info", "Le démon devra être redémarré après cette action": "Il demone dovrà essere riavviato dopo questa operazione.", diff --git a/core/i18n/pt_PT.json b/core/i18n/pt_PT.json index a5522ec..854e302 100644 --- a/core/i18n/pt_PT.json +++ b/core/i18n/pt_PT.json @@ -1,7 +1,8 @@ { "plugins\/tvremote\/core\/ajax\/tvremote.ajax.php": { "401 - Accès non autorisé": "401 - Acesso não autorizado", - "Aucune méthode correspondante à": "Nenhum método correspondente a" + "Aucune méthode correspondante à": "Nenhum método correspondente a", + "Format de données invalide": "Formato de dados inválido" }, "plugins\/tvremote\/core\/class\/tvremote.class.php": { "0": "0", @@ -36,6 +37,7 @@ "HDMI 3": "HDMI 3", "HDMI 4": "HDMI 4", "Home": "Casa", + "Impossible d\\'enregistrer le fichier logfilters.json": "Não é possível guardar o ficheiro logfilters.json", "Impossible de lancer le démon, vérifiez le log": "Não é possível iniciar o daemon, verificar o registo", "Info": "Informação", "Input": "Entrada", @@ -84,6 +86,13 @@ "plugins\/tvremote\/core\/php\/jeetvremote.php": { "Vous n\\'etes pas autorisé à effectuer cette action": "Não está autorizado a efetuar esta ação" }, + "plugins\/tvremote\/desktop\/js\/logfilters.tvremote.js": { + "Erreur lors de la sauvegarde": "Erro ao guardar", + "Ex: Cannot connect (device may be offline)": "Ex.: Não é possível estabelecer ligação (o dispositivo pode estar offline)", + "Filtres sauvegardés": "Filtros guardados", + "Redémarrez le démon pour appliquer les changements": "Reinicie o daemon para aplicar as alterações", + "Supprimer": "Eliminar" + }, "plugins\/tvremote\/desktop\/js\/tvremote.js": { "ADB Shell": "ADB Shell", "Afficher": "Ver", @@ -116,6 +125,18 @@ "Échec de l'appairage ADB pour": "Falha no emparelhamento ADB para", "Échec de l'appairage TVRemote pour": "Falha no emparelhamento do TVRemote para" }, + "plugins\/tvremote\/desktop\/modal\/logfilters.tvremote.php": { + "401 - Accès non autorisé": "401 - Acesso não autorizado", + "Actif": "Ativo", + "Ajouter": "Adicionar", + "Définissez les patterns à intercepter et le niveau de log à appliquer. Cochez\/décochez pour activer\/désactiver une règle sans la perdre.": "Defina os padrões a interceptar e o nível de registo a aplicar. Assinale\/desassinale para ativar\/desativar uma regra sem a perder.", + "Niveau cible": "Nível pretendido", + "Pattern (sous-chaîne du message de log)": "Padrão (subcadeia da mensagem de registo)", + "Sauvegarder": "Guardar", + "Suggestions :": "Sugestões:", + "Tout activer": "Ativar tudo", + "Tout désactiver": "Desativar tudo" + }, "plugins\/tvremote\/desktop\/php\/tvremote.php": { "401 - Accès non autorisé": "401 - Acesso não autorizado", "ADB": "ADB", @@ -196,6 +217,8 @@ "Vous disposez de 5 minutes pour entrer le code après avoir cliqué sur Appairer.": "Tem 5 minutos para introduzir o código depois de clicar em Emparelhar." }, "plugins\/tvremote\/plugin_info\/configuration.php": { + "Active la personnalisation des niveaux de log. Les filtres sont définis dans la fenêtre de gestion dédiée.": "Ativa a personalização dos níveis de registo. Os filtros são definidos na janela de gestão dedicada.", + "Activer les filtres de logs": "Ativar os filtros de registos", "Certificat ADB": "Certificado ADB", "Certificat TVRemote": "Certificado TVRemote", "Cocher cette case désactivera les messages de mise à jour du plugin dans le centre de message": "Marcar esta caixa desativará as mensagens de atualização do plugin no centro de mensagens.", @@ -206,10 +229,12 @@ "Désactiver les messages de MàJ": "Desativar mensagens de atualização", "Error": "Erro", "Facteur multiplicateur des cycles du démon (Défaut = x1)": "Fator de multiplicação do ciclo de demonstração (Predefinição = x1)", + "Filtres de logs": "Filtros de registos", "Force la réinitialisation de PyEnv": "Forçar reinicialização do PyEnv", "Force la réinitialisation de Venv": "Forçar a reposição do Venv", "Force les mises à jour Systèmes": "Forçar actualizações do sistema", "Fréquence des cycles": "Frequência do ciclo", + "Gérer les filtres": "Gerir os filtros", "Identique au démon (défaut)": "Idêntico ao daemon (padrão)", "Info": "Informação", "Le démon devra être redémarré après cette action": "O daemon deverá ser reiniciado após esta ação.", diff --git a/data/filters/README.md b/data/filters/README.md new file mode 100644 index 0000000..f31918e --- /dev/null +++ b/data/filters/README.md @@ -0,0 +1 @@ +Log filters are stored here as `logfilters.json` (gitignored). diff --git a/desktop/js/logfilters.tvremote.js b/desktop/js/logfilters.tvremote.js new file mode 100644 index 0000000..4529a9f --- /dev/null +++ b/desktop/js/logfilters.tvremote.js @@ -0,0 +1,146 @@ +/* This file is part of Jeedom. + * + * Jeedom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Jeedom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jeedom. If not, see . + */ + +(() => { +'use strict' + +const AJAX_URL = 'plugins/tvremote/core/ajax/tvremote.ajax.php' +const LEVELS = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'DROP'] + +function buildLevelSelect(selected) { + const select = document.createElement('select') + select.className = 'form-control lf-level' + LEVELS.forEach(function(l) { + const opt = document.createElement('option') + opt.value = l + opt.textContent = l === 'DROP' ? '---' : l + opt.selected = l === selected + select.appendChild(opt) + }) + return select +} + +function addRow(item) { + item = item || {} + const container = document.getElementById('logFiltersContainer') + if (!container) return + const enabled = item.enabled !== false + const level = ((item.level || 'DEBUG') + '').toUpperCase() + + const row = document.createElement('tr') + row.className = 'logFilterItem' + + const tdCheck = document.createElement('td') + tdCheck.style.textAlign = 'center' + const cb = document.createElement('input') + cb.type = 'checkbox' + cb.className = 'lf-enabled' + cb.checked = enabled + tdCheck.appendChild(cb) + + const tdPattern = document.createElement('td') + const input = document.createElement('input') + input.type = 'text' + input.className = 'form-control lf-pattern' + input.placeholder = '{{Ex: Cannot connect (device may be offline)}}' + input.value = item.pattern || '' + tdPattern.appendChild(input) + + const tdLevel = document.createElement('td') + tdLevel.appendChild(buildLevelSelect(level)) + + const tdRemove = document.createElement('td') + tdRemove.style.textAlign = 'center' + const icon = document.createElement('i') + icon.className = 'fas fa-minus-circle lf-remove icon_red' + icon.style.cursor = 'pointer' + icon.title = '{{Supprimer}}' + tdRemove.appendChild(icon) + + row.append(tdCheck, tdPattern, tdLevel, tdRemove) + container.appendChild(row) +} + +function collectRows() { + const rows = document.querySelectorAll('.logFilterItem') + const result = [] + rows.forEach(function(row) { + const pattern = row.querySelector('.lf-pattern').value.trim() + if (!pattern) return + result.push({ + enabled: row.querySelector('.lf-enabled').checked, + pattern: pattern, + level: row.querySelector('.lf-level').value + }) + }) + return result +} + +window.initLogFilters = function(data) { + const dataArray = Array.isArray(data) ? data : [] + dataArray.forEach(addRow) + + const addBtn = document.getElementById('bt_addLogFilter') + if (addBtn) addBtn.addEventListener('click', function() { addRow({}) }) + + const container = document.getElementById('logFiltersContainer') + if (container) { + container.addEventListener('click', function(e) { + const btn = e.target.closest('.lf-remove') + if (btn) btn.closest('.logFilterItem').remove() + }) + } + + const selectAllBtn = document.getElementById('bt_selectAllLogFilters') + if (selectAllBtn) selectAllBtn.addEventListener('click', function() { + document.querySelectorAll('.lf-enabled').forEach(function(cb) { cb.checked = true }) + }) + + const unselectAllBtn = document.getElementById('bt_unselectAllLogFilters') + if (unselectAllBtn) unselectAllBtn.addEventListener('click', function() { + document.querySelectorAll('.lf-enabled').forEach(function(cb) { cb.checked = false }) + }) + + document.querySelectorAll('.tvremote-logfilter-suggest').forEach(function(link) { + link.addEventListener('click', function(e) { + e.preventDefault() + addRow({ pattern: link.dataset.pattern, level: link.dataset.level, enabled: true }) + }) + }) + + const saveBtn = document.getElementById('bt_saveLogFilters') + if (saveBtn) saveBtn.addEventListener('click', function() { + const filters = collectRows() + domUtils.ajax({ + type: 'POST', + url: AJAX_URL, + data: { action: 'saveLogFilters', logFiltersData: JSON.stringify(filters) }, + dataType: 'json', + error: function(request) { + jeedomUtils.showAlert({ message: (request.responseJSON && request.responseJSON.result) || '{{Erreur lors de la sauvegarde}}', level: 'danger' }) + }, + success: function(data) { + if (data.state !== 'ok') { + jeedomUtils.showAlert({ message: data.result, level: 'danger' }) + return + } + jeedomUtils.showAlert({ message: '{{Filtres sauvegardés}} — {{Redémarrez le démon pour appliquer les changements}}', level: 'success' }) + } + }) + }) +} + +})() diff --git a/desktop/modal/logfilters.tvremote.php b/desktop/modal/logfilters.tvremote.php new file mode 100644 index 0000000..f20b376 --- /dev/null +++ b/desktop/modal/logfilters.tvremote.php @@ -0,0 +1,44 @@ + + +
+ + + {{Définissez les patterns à intercepter et le niveau de log à appliquer. Cochez/décochez pour activer/désactiver une règle sans la perdre.}} + + + {{Sauvegarder}} + +
+ +
+ {{Ajouter}} + + +
+ + + + + + + + + + + +
{{Actif}}{{Pattern (sous-chaîne du message de log)}}{{Niveau cible}}
+ +
+ {{Suggestions :}} + Cannot connect (device may be offline) → DEBUG +
+ + + diff --git a/plugin_info/configuration.php b/plugin_info/configuration.php index 683bd55..ffb23ba 100644 --- a/plugin_info/configuration.php +++ b/plugin_info/configuration.php @@ -129,6 +129,20 @@ +
+ +
+ +
+ +
@@ -185,5 +199,17 @@ function setupResetButton(selector, action, successMessage) { setupResetButton('.customclass-resettvcertkey', 'resetTVCertKey', '{{Reset TV Cert (OK)}}') setupResetButton('.customclass-resetadbkeys', 'resetAdbKeys', '{{Reset ADB Keys (OK)}}') + + // Filtres de logs + document.querySelector('.customclass-openlogfilters')?.addEventListener('click', function() { + jeeDialog.dialog({ + id: 'md_logFiltersTvremote', + title: '{{Filtres de logs}}', + contentUrl: 'index.php?v=d&plugin=tvremote&modal=logfilters.tvremote', + width: '65%', + height: '60%', + top: '18vh' + }) + }) })() diff --git a/plugin_info/info.json b/plugin_info/info.json index 8fb389a..3c6f800 100644 --- a/plugin_info/info.json +++ b/plugin_info/info.json @@ -1,7 +1,7 @@ { "id": "tvremote", "name": "TV Remote", - "pluginVersion": "1.3.21", + "pluginVersion": "1.4.1", "description": { "fr_FR": "Plugin pour contrôler les équipements Android de type Télévision (Sony, Nvidia Shield, Freebox TV, etc...) via le service de télécommande Google (TVRemote) ou via ADB. Il permet de contrôler les différentes fonctions de sa télévision (power, volume, entrées HDMI, lancement d'applications, chaînes TV, navigation dans l'interface, etc...)", "en_US": "Plugin to control Android TV devices (Sony, Nvidia Shield, Freebox TV, etc...) via Google remote control service (TVRemote) or via ADB. It allows you to control various functions of your TV (power, volume, HDMI inputs, launching applications, TV channels, navigating the interface, etc...)", diff --git a/plugin_info/install.php b/plugin_info/install.php index 2916daa..06c9ea4 100644 --- a/plugin_info/install.php +++ b/plugin_info/install.php @@ -55,6 +55,11 @@ function tvremote_install() { if (config::byKey('disableUpdateMsg', 'tvremote') == '') { config::save('disableUpdateMsg', '0', 'tvremote'); } + if (config::byKey('logFiltersEnabled', 'tvremote') == '') { + config::save('logFiltersEnabled', '0', 'tvremote'); + } + + tvremote::createLogFiltersFile(); $dependencyInfo = tvremote::dependancy_info(); if (!isset($dependencyInfo['state'])) { @@ -108,6 +113,11 @@ function tvremote_update() { if (config::byKey('disableUpdateMsg', 'tvremote') == '') { config::save('disableUpdateMsg', '0', 'tvremote'); } + if (config::byKey('logFiltersEnabled', 'tvremote') == '') { + config::save('logFiltersEnabled', '0', 'tvremote'); + } + + tvremote::createLogFiltersFile(); $dependencyInfo = tvremote::dependancy_info(); if (!isset($dependencyInfo['state'])) { diff --git a/resources/tvremoted/config.py b/resources/tvremoted/config.py index 9b7ee9c..9550359 100644 --- a/resources/tvremoted/config.py +++ b/resources/tvremoted/config.py @@ -25,6 +25,11 @@ def __init__(self, **kwargs): self.known_hosts = [] # Hosts for AndroidTVRemote2 self.known_hosts_adb = [] # Hosts for ADB self.remote_mac = [] # MAC addresses for AndroidTVRemote2 + + self.log_filters_enabled: bool = False + self.log_filters: list = [] # [{ "pattern": str, "level": str, "enabled": bool }] + self.log_filters_file_path = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'data/filters/logfilters.json')) + self.remote_mac_adb = [] # MAC addresses for ADB self.remote_names = [] # Names for AndroidTVRemote2 self.remote_names_adb = [] # Names for ADB @@ -126,6 +131,10 @@ def log_level(self): def tv_log_level(self): return self._kwargs.get('tvloglevel', 'daemon') + @property + def log_filters_enabled_arg(self): + return self._kwargs.get('logfiltersenabled', '0') + @property def api_key(self): return self._kwargs.get('apikey', '') diff --git a/resources/tvremoted/tvremoted.py b/resources/tvremoted/tvremoted.py index 0334c47..5995efa 100644 --- a/resources/tvremoted/tvremoted.py +++ b/resources/tvremoted/tvremoted.py @@ -171,7 +171,7 @@ async def main(self) -> None: self._logger.debug(traceback.format_exc()) def is_available_updated(is_available: bool) -> None: - self._logger.info("[EQRemote][Is_Available][%s] Notification :: %s", self._macAddr, is_available) + self._logger.debug("[EQRemote][Is_Available][%s] Notification :: %s", self._macAddr, is_available) try: # UpdateLastTime currentTime = int(time.time()) @@ -193,7 +193,7 @@ def is_available_updated(is_available: bool) -> None: self._logger.debug(traceback.format_exc()) def is_on_updated(is_on: bool) -> None: - self._logger.info("[EQRemote][Is_On][%s] Notification :: %s", self._macAddr, is_on) + self._logger.debug("[EQRemote][Is_On][%s] Notification :: %s", self._macAddr, is_on) try: # UpdateLastTime currentTime = int(time.time()) @@ -215,7 +215,7 @@ def is_on_updated(is_on: bool) -> None: self._logger.debug(traceback.format_exc()) def current_app_updated(current_app: str) -> None: - self._logger.info("[EQRemote][Current_App][%s] Notification :: %s", self._macAddr, current_app) + self._logger.debug("[EQRemote][Current_App][%s] Notification :: %s", self._macAddr, current_app) try: # UpdateLastTime currentTime = int(time.time()) @@ -236,7 +236,7 @@ def current_app_updated(current_app: str) -> None: self._logger.debug(traceback.format_exc()) def volume_info_updated(volume_info: VolumeInfo) -> None: - self._logger.info("[EQRemote][Volume_Info][%s] Notification :: %s", self._macAddr, volume_info) + self._logger.debug("[EQRemote][Volume_Info][%s] Notification :: %s", self._macAddr, volume_info) try: # UpdateLastTime currentTime = int(time.time()) @@ -690,7 +690,7 @@ async def send_command(self, action: str | None = None, value: str | None = None # Connect on-demand if not already connected if not self._persistent_connection and not self._connected: - self._logger.info("[EQRemoteADB][SendCommand] On-demand mode: connecting...") + self._logger.debug("[EQRemoteADB][SendCommand] On-demand mode: connecting...") if self._adb is None: self._adb = AdbDeviceTcpAsync(self._host, self._port, default_transport_timeout_s=self._config.adb_timeout) @@ -1554,10 +1554,44 @@ def close(self) -> None: # ---------------------------------------------------------------------------- +class PatternRemapFilter(logging.Filter): + """Remappe le niveau de log des records dont le message contient un pattern configuré.""" + + _LEVEL_MAP = { + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARNING': logging.WARNING, + 'ERROR': logging.ERROR, + } + + def __init__(self, entries: list) -> None: + super().__init__() + self._rules = [ + (e['pattern'], e['level'].upper()) + for e in entries + if e.get('enabled', True) and e.get('pattern', '').strip() + ] + + def filter(self, record: logging.LogRecord) -> bool: + msg = record.getMessage() + for pattern, level in self._rules: + if pattern in msg: + if level == 'DROP': + return False + target = self._LEVEL_MAP.get(level) + if target is not None: + record.levelno = target + record.levelname = level + return True + return True + +# ---------------------------------------------------------------------------- + def get_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description='TVRemote Daemon for Jeedom plugin') parser.add_argument("--loglevel", help="Log Level for the daemon", type=str) parser.add_argument("--tvloglevel", help="Log Level for TV libraries (zeroconf, androidtvremote2, adb_shell)", type=str, default='daemon') + parser.add_argument("--logfiltersenabled", help="Enable log filters", type=str, default='0') parser.add_argument("--pluginversion", help="Plugin Version", type=str) parser.add_argument("--socketport", help="Port for TVRemote server", type=str) parser.add_argument("--cyclefactor", help="Cycle Factor", type=str) @@ -1598,12 +1632,32 @@ def shutdown() -> None: logging.getLogger('androidtvremote2').setLevel(_tv_level) logging.getLogger('adb_shell').setLevel(_tv_level) +if config.log_filters_enabled_arg != '0': + config.log_filters_enabled = True + +if config.log_filters_enabled: + try: + with open(config.log_filters_file_path, 'r', encoding='utf-8') as _f: + config.log_filters = json.load(_f) + except FileNotFoundError: + logging.warning('[DAEMON][LOGFILTERS] Fichier introuvable : %s', config.log_filters_file_path) + except Exception as _e: + logging.warning('[DAEMON][LOGFILTERS] Erreur lecture filtres : %s', _e) + +if config.log_filters_enabled and config.log_filters: + _remap_filter = PatternRemapFilter(config.log_filters) + for _handler in logging.root.handlers: + _handler.addFilter(_remap_filter) + _active_count = sum(1 for r in config.log_filters if r.get('enabled', True)) + logging.info('[DAEMON][LOGFILTERS] %d filtre(s) actif(s) sur %d', _active_count, len(config.log_filters)) + try: _LOGGER.info('[DAEMON] Starting Daemon') _LOGGER.info('[DAEMON] Plugin Version: %s', config.plugin_version) _LOGGER.info('[DAEMON] Pairing Name: %s', config.client_name) _LOGGER.info('[DAEMON] Log Level: %s', config.log_level) _LOGGER.info('[DAEMON] TV Log Level: %s', config.tv_log_level) + _LOGGER.info('[DAEMON] Log Filters Enabled: %s | rules: %d', config.log_filters_enabled, len(config.log_filters)) _LOGGER.info('[DAEMON] Socket Port: %s', config.socket_port) _LOGGER.info('[DAEMON] Socket Host: %s', config.socket_host) _LOGGER.info('[DAEMON] Cycle Factor: %s', config.cycle_factor)