corrige temperature

This commit is contained in:
2026-01-10 20:24:11 +01:00
parent ff3fc65eef
commit 91bfcff1fe
2486 changed files with 31797 additions and 544 deletions

View File

@@ -0,0 +1,147 @@
# Historique Session - 30 Décembre 2025
## Résumé
Session de développement pour aligner le payload MQTT discovery de Pilot v2 avec la spécification Home Assistant et le format v1 qui fonctionne.
## Objectif
Faire fonctionner la découverte automatique Home Assistant pour le device "asus" sur le broker MQTT 10.0.0.3:1883.
## Problèmes identifiés et résolus
### 1. Configuration MQTT ✅
- **Problème**: Device configuré avec $hostname, broker à 10.0.0.3:1883
- **Solution**: Configuration validée dans `pilot-v2/config.yaml`
- **Résultat**: Hostname "asus" correctement expansé
### 2. Device Info non configurable ✅
- **Problème**: manufacturer, model, sw_version hardcodés
- **Solution**: Ajout de ces champs dans `config/mod.rs` avec valeurs par défaut
- **Commit**: `df871dd` - "Fix Home Assistant MQTT discovery compliance"
### 3. Payload discovery non conforme ✅
- **Problème initial**: Plusieurs champs manquants par rapport à v1
- **Corrections apportées**:
- ✅ Ajout `payload_available: "online"` et `payload_not_available: "offline"`
- ✅ Suppression device_class "power" incorrect pour cpu_usage
- ✅ Ajout puis suppression du champ `"type"` (non requis par HA spec)
- ✅ Topics de découverte: `homeassistant/{component}/{node_id}/{entity_name}/config`
- ✅ Entity naming: `{metric}_{device}` (ex: cpu_usage_asus)
### 4. Topics MQTT restructurés ✅
- **Sensors**:
- State topic: `pilot/{device}/{metric}` (sans /state/)
- Availability: `pilot/{device}/{metric}/available`
- **Switches**:
- State topic: `pilot/{device}/{metric}/state` (avec /state)
- Availability: `pilot/{device}/{metric}/available`
- Command topic: `pilot/{device}/cmd/{action}/set`
### 5. Fonction publish_switch_state ajoutée ✅
- **Raison**: Sensors et switches ont des formats de topics différents
- **Fichier**: `pilot-v2/src/mqtt/mod.rs`
- **Fonction**: Ajoute `/state` à la fin du topic pour les switches
## Structure du payload final (conforme HA)
```json
{
"name": "cpu_usage_asus",
"unique_id": "asus_cpu_usage",
"state_topic": "pilot/asus/cpu_usage",
"availability_topic": "pilot/asus/cpu_usage/available",
"payload_available": "online",
"payload_not_available": "offline",
"device": {
"identifiers": ["asus"],
"name": "asus",
"manufacturer": "Asus",
"model": "Laptop",
"sw_version": "2.0.0",
"suggested_area": "Bureau"
},
"unit_of_measurement": "%",
"icon": "mdi:chip"
}
```
**Topic de publication**: `homeassistant/sensor/asus/cpu_usage_asus/config`
## Commits créés
1. **df871dd** - Fix Home Assistant MQTT discovery compliance
- Ajout payload_available/not_available
- Device info configurable
- Correction device_class
2. **60b622c** - Align MQTT discovery with v1 format and HA specification
- Restructuration topics (sensors vs switches)
- Fonction publish_switch_state
- Entity naming corrigé
3. **ff3fc65** - Remove 'type' field from discovery payload per HA spec
- Suppression du champ "type" non requis
## Problème en cours ⚠️
### Connexion MQTT refuse (Connection refused os error 111)
- **Symptôme**: `rumqttc` ne parvient pas à se connecter au broker 10.0.0.3:1883
- **Tests effectués**:
-`nc -zv 10.0.0.3 1883` → Port ouvert
-`telnet 10.0.0.3 1883` → Connexion réussie
- ❌ Pilot v2 → Connection refused
- **Note importante**: L'utilisateur a confirmé avoir VU le device dans HA, mais il est maintenant déconnecté
- **Hypothèse**: Une instance précédente a réussi à se connecter et publier la découverte, puis s'est déconnectée
### Actions à faire après redémarrage
1. Vérifier que le broker MQTT est bien démarré
2. Vérifier s'il y a des logs du broker expliquant les refus de connexion
3. Tester si une autre application (Python v1) arrive à se connecter depuis "asus"
4. Vérifier les pare-feu locaux
5. Essayer de relancer Pilot et vérifier les logs détaillés
## Fichiers modifiés
```
pilot-v2/src/ha/mod.rs - Structure discovery, topics, payload
pilot-v2/src/mqtt/mod.rs - Fonction publish_switch_state
pilot-v2/src/runtime/mod.rs - Utilisation publish_switch_state
pilot-v2/src/config/mod.rs - Device info configurable
pilot-v2/config.yaml - Configuration asus
config/config.example.yaml - Documentation device info
```
## État actuel
- ✅ Payload discovery 100% conforme à HA spec
- ✅ Tous les topics correctement formatés
- ✅ Code compilé sans erreur
- ✅ Device détecté dans HA (confirmation utilisateur)
- ❌ Problème de connexion MQTT persistant
## Commandes utiles pour debug
```bash
# Vérifier si Pilot tourne
ps aux | grep pilot-v2
# Tester la connexion MQTT
nc -zv 10.0.0.3 1883
# Lancer Pilot avec logs
RUST_LOG=debug ./pilot-v2/target/release/pilot-v2
# Voir les logs
tail -f /tmp/pilot_reconnect.log
# Tuer toutes les instances
pkill -9 pilot-v2
```
## Prochaines étapes
1. **Résoudre le problème de connexion MQTT**
2. Vérifier que le device reste "online" dans HA
3. Tester les commandes (shutdown, reboot, etc.)
4. Désactiver dry_run mode si les tests sont concluants
5. Configurer le service systemd pour démarrage automatique

View File

@@ -0,0 +1,77 @@
# Historique Dev - 30 Décembre 2025 (reboot)
## Contexte
Objectif: stabiliser Pilot v2 (MQTT discovery HA, telemetry avancée, commandes), ajouter métriques détaillées configurables par YAML, et logs debug.
## État courant (ok)
- MQTT OK, topics publiés, discovery HA pilotée par config.
- Avail: `pilot/<device>/availability` utilisé pour toutes les entités.
- Logs debug ajoutés sur publications MQTT + discovery.
- `RUST_LOG` respecté (env filter)
- Commandes MQTT reçues et logs des commandes.
## Problèmes rencontrés
- Déconnexions MQTT “connection closed by peer” → mitigé en doublant les intervals + backoff
- Ancien topics retained (`*_mb`, `pilot/<device>/state/...`) visibles: nettoyage nécessaire (mqtt_pub -r -n)
- Écran: backend `gnome_busctl` non dispo (SetPowerSaveMode absent). `x11_xset` OK en X11.
## Changements principaux dans le code
- **Config par métrique** (interval, unit, device_class, name, unique_id, icon, state_class, discovery_enabled)
- **Scheduling par métrique** (intervals distincts)
- **Telemetry**:
- CPU/mem/disk/IP/battery/power
- GPU NVIDIA (gpu0/gpu1 usage, temp, mem) via `nvidia-smi`
- AMD iGPU (usage via sysfs, mem via sysfs, temp via sysinfo components)
- Temp CPU/SSD via sysinfo components
- Fans CPU/GPU via `/sys/class/hwmon`
- OS/kernel via sysinfo
- Process metrics `pilot_v2_cpu_usage` et `pilot_v2_mem_used_mb`
- **Logs**:
- publish_state / publish_switch_state / publish_discovery
- mqtt incoming publish + command received
- x11 env logs + stderr for screen command
- publish stats (nb msg/min)
- backoff reconnect
## Config YAML (nouveau format)
- `features.telemetry.metrics.<metric>` (dict)
- `enabled`, `discovery_enabled`, `interval_s`, `unit`, `name`, `unique_id`, `icon`, `device_class`, `state_class`
- `$hostname` remplacé dans name/unique_id
## Metrics ajoutées (principales)
- Temps: `cpu_temp_c`, `ssd_temp_c`, `gpu0_temp_c`, `gpu1_temp_c`, `amd_gpu_temp_c`
- GPU NVIDIA: `gpu0_usage`, `gpu1_usage`, `gpu0_mem_used_gb`, `gpu1_mem_used_gb`, `gpu_usage` (moyenne)
- AMD iGPU: `amd_gpu_usage`, `amd_gpu_mem_used_gb`
- Fans: `fan_cpu_rpm`, `fan_gpu_rpm`
- Process: `pilot_v2_cpu_usage`, `pilot_v2_mem_used_mb`
## Fichiers modifiés (principaux)
- `pilot-v2/src/config/mod.rs`
- `pilot-v2/src/runtime/mod.rs`
- `pilot-v2/src/telemetry/mod.rs`
- `pilot-v2/src/ha/mod.rs`
- `pilot-v2/src/mqtt/mod.rs`
- `pilot-v2/src/platform/linux/mod.rs`
- `pilot-v2/src/main.rs`
- `pilot-v2/config.yaml`
- `config.yaml`
- `config/config.example.yaml`
## Commandes utiles
- Build + run:
- `cargo build --release`
- `RUST_LOG=pilot_v2=debug,rumqttc=debug ./target/release/pilot-v2`
- MQTT test:
- `mosquitto_sub -h 10.0.0.3 -p 1883 -t 'pilot/asus/#' -v`
- Screen OFF (x11):
- `mosquitto_pub -h 10.0.0.3 -p 1883 -t 'pilot/asus/cmd/screen/set' -m 'OFF'`
- Check fans:
- `cat /sys/class/hwmon/hwmon10/fan1_input`
- `cat /sys/class/hwmon/hwmon10/fan2_input`
- GPU metrics:
- `nvidia-smi --query-gpu=utilization.gpu,memory.used,temperature.gpu --format=csv,noheader,nounits`
## Notes
- `dry_run` doit être false pour exécuter les commandes.
- `x11_xset` requis pour écran en X11.
- GPU1 peut rester “Inconnu” si inactive (hybride).

View File

@@ -0,0 +1,43 @@
Tu es Claude Code. Tu dois creer une extension GNOME Shell (GNOME 45+) simple, pour un debutant, qui permet de piloter l'app Pilot V2 depuis GNOME Shell.
Contexte general (application) :
- Pilot V2 est un agent qui publie des capteurs (telemetry) et recoit des commandes via MQTT.
- Sa configuration principale est dans `pilot-v2/config.yaml`.
- Les sections importantes du YAML :
- `features.telemetry.enabled` + `features.telemetry.metrics.<metric>.enabled`
- `features.commands.enabled` + `features.commands.allowlist`
- `mqtt.*` pour la connexion (host, port, user, etc.)
- L'app tourne comme service systemd (fichier `mqtt_pilot.service`) et peut etre demarree/arretee.
- Objectif utilisateur : pouvoir activer/desactiver rapidement les services, chaque capteur, et chaque commande, sans editer le YAML a la main.
Objectif V1 (indispensable) :
- Tout est accessible depuis GNOME Shell.
- L'extension affiche une liste d'entites (services + capteurs + commandes).
- Chaque ligne = nom + switch ON/OFF + bouton "..." pour editer.
- Le switch active/desactive l'entite dans `pilot-v2/config.yaml`.
- Le bouton "..." ouvre un dialogue pour editer les champs essentiels :
- Telemetry: `enabled`, `interval_s`, `name`, `unique_id`
- Commands: `enabled` + gestion de l'allowlist
- Service: enable/disable global (choisir un champ clair a creer si absent)
- IMPORTANT: Toute modification doit recharger la config pour que l'app la prenne en compte
(ex: redemarrage du service systemd ou mecanisme de reload si dispo).
Details techniques :
- GNOME 45+, GJS + libadwaita (GTK4).
- Extension "panel menu" (icone en haut a droite) avec un menu qui ouvre une fenetre principale.
- La fenetre principale affiche des sections : Services, Telemetry (sensors), Commands.
- V1 = lecture/ecriture directe du YAML, pas d'API reseau, pas de base de donnees.
- Apres modification, recharger les donnees dans l'UI.
Contraintes :
- Code clair, commente pour debutant.
- Ne pas inventer d'API reseau.
- Ecrire les fichiers de l'extension avec une structure standard GNOME (metadata.json, extension.js, prefs.js, etc.).
- Donner aussi les commandes pour installer l'extension en local.
Sortie attendue :
- Arborescence des fichiers.
- Code complet des fichiers.
- Instructions d'installation et de test.
Commence par expliquer rapidement ton plan d'implementation, puis donne le code.

View File

@@ -1,16 +1,20 @@
# Codex created 2025-12-29_0224
device:
name: pilot-device
identifiers: ["pilot-device"]
name: $hostname
identifiers: ["$hostname"]
manufacturer: "Asus"
model: "Laptop"
sw_version: "2.0.0"
suggested_area: "Bureau"
mqtt:
host: "127.0.0.1"
host: "10.0.0.3"
port: 1883
username: ""
password: ""
base_topic: "pilot"
discovery_prefix: "homeassistant"
client_id: "pilot-device"
client_id: "$hostname"
keepalive_s: 60
qos: 0
retain_states: true
@@ -18,7 +22,267 @@ mqtt:
features:
telemetry:
enabled: true
interval_s: 10
metrics:
cpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "CPU Usage"
unique_id: "pilot-device_cpu_usage"
unit: "%"
device_class: ""
icon: "mdi:chip"
state_class: "measurement"
pilot_v2_cpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "Pilot V2 CPU Usage"
unique_id: "pilot-device_pilot_v2_cpu_usage"
unit: "%"
device_class: ""
icon: "mdi:application"
state_class: "measurement"
pilot_v2_mem_used_mb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "Pilot V2 Memory Used"
unique_id: "pilot-device_pilot_v2_mem_used"
unit: "MB"
device_class: ""
icon: "mdi:application"
state_class: "measurement"
cpu_temp_c:
enabled: true
discovery_enabled: true
interval_s: 10
name: "CPU Temp"
unique_id: "pilot-device_cpu_temp"
unit: "C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
ssd_temp_c:
enabled: true
discovery_enabled: true
interval_s: 60
name: "SSD Temp"
unique_id: "pilot-device_ssd_temp"
unit: "C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
gpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU Usage"
unique_id: "pilot-device_gpu_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
gpu0_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU0 Usage"
unique_id: "pilot-device_gpu0_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
gpu1_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU1 Usage"
unique_id: "pilot-device_gpu1_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
gpu0_temp_c:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU0 Temp"
unique_id: "pilot-device_gpu0_temp"
unit: "C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
gpu1_temp_c:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU1 Temp"
unique_id: "pilot-device_gpu1_temp"
unit: "C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
gpu0_mem_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU0 Memory Used"
unique_id: "pilot-device_gpu0_mem_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
gpu1_mem_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU1 Memory Used"
unique_id: "pilot-device_gpu1_mem_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
amd_gpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "AMD GPU Usage"
unique_id: "pilot-device_amd_gpu_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
amd_gpu_temp_c:
enabled: true
discovery_enabled: true
interval_s: 10
name: "AMD GPU Temp"
unique_id: "pilot-device_amd_gpu_temp"
unit: "C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
amd_gpu_mem_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "AMD GPU Memory Used"
unique_id: "pilot-device_amd_gpu_mem_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
memory_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "Memory Used"
unique_id: "pilot-device_memory_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
memory_total_gb:
enabled: true
discovery_enabled: true
interval_s: 120
name: "Memory Total"
unique_id: "pilot-device_memory_total"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: ""
disk_free_gb:
enabled: true
discovery_enabled: true
interval_s: 120
name: "Disk Free"
unique_id: "pilot-device_disk_free"
unit: "GB"
device_class: ""
icon: "mdi:harddisk"
state_class: "measurement"
fan_cpu_rpm:
enabled: true
discovery_enabled: true
interval_s: 10
name: "CPU Fan"
unique_id: "pilot-device_fan_cpu"
unit: "RPM"
device_class: ""
icon: "mdi:fan"
state_class: "measurement"
fan_gpu_rpm:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU Fan"
unique_id: "pilot-device_fan_gpu"
unit: "RPM"
device_class: ""
icon: "mdi:fan"
state_class: "measurement"
ip_address:
enabled: true
discovery_enabled: true
interval_s: 120
name: "IP Address"
unique_id: "pilot-device_ip"
unit: ""
device_class: ""
icon: "mdi:ip"
state_class: ""
battery_level:
enabled: true
discovery_enabled: true
interval_s: 60
name: "Battery Level"
unique_id: "pilot-device_battery_level"
unit: "%"
device_class: "battery"
icon: "mdi:battery"
state_class: "measurement"
battery_state:
enabled: true
discovery_enabled: true
interval_s: 60
name: "Battery State"
unique_id: "pilot-device_battery_state"
unit: ""
device_class: ""
icon: "mdi:battery-charging"
state_class: ""
power_state:
enabled: true
discovery_enabled: true
interval_s: 60
name: "Power State"
unique_id: "pilot-device_power_state"
unit: ""
device_class: ""
icon: "mdi:power"
state_class: ""
kernel:
enabled: true
discovery_enabled: true
interval_s: 7200
name: "Kernel"
unique_id: "pilot-device_kernel"
unit: ""
device_class: ""
icon: "mdi:linux"
state_class: ""
os_version:
enabled: true
discovery_enabled: true
interval_s: 7200
name: "OS Version"
unique_id: "pilot-device_os_version"
unit: ""
device_class: ""
icon: "mdi:desktop-classic"
state_class: ""
commands:
enabled: true
cooldown_s: 5

View File

@@ -26,7 +26,267 @@ mqtt:
features:
telemetry:
enabled: true
interval_s: 10
metrics:
cpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "CPU Usage"
unique_id: "$hostname_cpu_usage"
unit: "%"
device_class: ""
icon: "mdi:chip"
state_class: "measurement"
pilot_v2_cpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "Pilot V2 CPU Usage"
unique_id: "$hostname_pilot_v2_cpu_usage"
unit: "%"
device_class: ""
icon: "mdi:application"
state_class: "measurement"
pilot_v2_mem_used_mb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "Pilot V2 Memory Used"
unique_id: "$hostname_pilot_v2_mem_used"
unit: "MB"
device_class: ""
icon: "mdi:application"
state_class: "measurement"
cpu_temp_c:
enabled: true
discovery_enabled: true
interval_s: 10
name: "CPU Temp"
unique_id: "$hostname_cpu_temp"
unit: "C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
ssd_temp_c:
enabled: true
discovery_enabled: true
interval_s: 60
name: "SSD Temp"
unique_id: "$hostname_ssd_temp"
unit: "C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
gpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU Usage"
unique_id: "$hostname_gpu_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
gpu0_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU0 Usage"
unique_id: "$hostname_gpu0_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
gpu1_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU1 Usage"
unique_id: "$hostname_gpu1_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
gpu0_temp_c:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU0 Temp"
unique_id: "$hostname_gpu0_temp"
unit: "C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
gpu1_temp_c:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU1 Temp"
unique_id: "$hostname_gpu1_temp"
unit: "C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
gpu0_mem_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU0 Memory Used"
unique_id: "$hostname_gpu0_mem_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
gpu1_mem_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU1 Memory Used"
unique_id: "$hostname_gpu1_mem_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
amd_gpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "AMD GPU Usage"
unique_id: "$hostname_amd_gpu_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
amd_gpu_temp_c:
enabled: true
discovery_enabled: true
interval_s: 10
name: "AMD GPU Temp"
unique_id: "$hostname_amd_gpu_temp"
unit: "C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
amd_gpu_mem_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "AMD GPU Memory Used"
unique_id: "$hostname_amd_gpu_mem_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
memory_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "Memory Used"
unique_id: "$hostname_memory_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
memory_total_gb:
enabled: true
discovery_enabled: true
interval_s: 120
name: "Memory Total"
unique_id: "$hostname_memory_total"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: ""
disk_free_gb:
enabled: true
discovery_enabled: true
interval_s: 120
name: "Disk Free"
unique_id: "$hostname_disk_free"
unit: "GB"
device_class: ""
icon: "mdi:harddisk"
state_class: "measurement"
fan_cpu_rpm:
enabled: true
discovery_enabled: true
interval_s: 10
name: "CPU Fan"
unique_id: "$hostname_fan_cpu"
unit: "RPM"
device_class: ""
icon: "mdi:fan"
state_class: "measurement"
fan_gpu_rpm:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU Fan"
unique_id: "$hostname_fan_gpu"
unit: "RPM"
device_class: ""
icon: "mdi:fan"
state_class: "measurement"
ip_address:
enabled: true
discovery_enabled: true
interval_s: 120
name: "IP Address"
unique_id: "$hostname_ip"
unit: ""
device_class: ""
icon: "mdi:ip"
state_class: ""
battery_level:
enabled: true
discovery_enabled: true
interval_s: 60
name: "Battery Level"
unique_id: "$hostname_battery_level"
unit: "%"
device_class: "battery"
icon: "mdi:battery"
state_class: "measurement"
battery_state:
enabled: true
discovery_enabled: true
interval_s: 60
name: "Battery State"
unique_id: "$hostname_battery_state"
unit: ""
device_class: ""
icon: "mdi:battery-charging"
state_class: ""
power_state:
enabled: true
discovery_enabled: true
interval_s: 60
name: "Power State"
unique_id: "$hostname_power_state"
unit: ""
device_class: ""
icon: "mdi:power"
state_class: ""
kernel:
enabled: true
discovery_enabled: true
interval_s: 7200
name: "Kernel"
unique_id: "$hostname_kernel"
unit: ""
device_class: ""
icon: "mdi:linux"
state_class: ""
os_version:
enabled: true
discovery_enabled: true
interval_s: 7200
name: "OS Version"
unique_id: "$hostname_os_version"
unit: ""
device_class: ""
icon: "mdi:desktop-classic"
state_class: ""
commands:
enabled: true
cooldown_s: 5

View File

@@ -11,6 +11,7 @@
## P1
- ajout d'une interface graphique pour simplifier reglages cinfig.yaml ( pour gnome en 1er)
- extension GNOME Shell pour activer/desactiver l'app et gerer commandes/telemetry
- TLS + ACL broker.
- Home Assistant discovery complet.
- Telemetry GPU multi-vendors.

View File

@@ -0,0 +1,209 @@
# Comment lancer l'extension Pilot Control
## ✅ L'extension est installée et corrigée !
L'extension a été mise à jour pour supporter GNOME Shell 48.
## 🚀 Étapes pour utiliser l'extension
### 1. Redémarrer GNOME Shell
**Sur X11 (recommandé si vous êtes sur X11) :**
```bash
# Méthode 1 : Via le dialogue "Exécuter"
# Appuyez sur Alt+F2
# Tapez : r
# Appuyez sur Entrée
```
**Sur Wayland :**
```bash
# Déconnectez-vous et reconnectez-vous
# Ou redémarrez votre session
```
**Alternative - Redémarrer complètement :**
```bash
# Si les méthodes ci-dessus ne fonctionnent pas
sudo systemctl restart gdm
# Ou redémarrez l'ordinateur
```
### 2. Vérifier que l'extension est activée
```bash
# Vérifier le statut
gnome-extensions list --enabled | grep pilot
# Si elle n'est pas activée, l'activer :
gnome-extensions enable pilot-control@gnome-shell-extensions
```
### 3. Localiser l'icône dans le panel
Après le redémarrage de GNOME Shell, vous devriez voir :
```
┌─────────────────────────────────────────────────┐
│ [WiFi] [Volume] [🖥️ Computer] [Power] [Clock] │
│ ↑ │
│ └─ Icône Pilot Control │
└─────────────────────────────────────────────────┘
```
L'icône ressemble à un **ordinateur** (computer-symbolic).
### 4. Utiliser l'extension
**Option 1 : Menu rapide**
1. Cliquez sur l'icône d'ordinateur
2. Vous verrez le menu :
```
Status: 🟢 Running (ou 🔴 Stopped)
──────────────────────
Open Control Panel
──────────────────────
Start Service
Stop Service
Restart Service
──────────────────────
Reload Config
```
**Option 2 : Fenêtre complète**
1. Cliquez sur l'icône
2. Cliquez sur "Open Control Panel"
3. La fenêtre principale s'ouvre avec 3 sections :
- **Service Control** : Gérer le service
- **Telemetry Metrics** : Gérer les métriques
- **Commands** : Gérer les commandes autorisées
## 🔍 Dépannage
### L'icône n'apparaît pas
```bash
# 1. Vérifier que l'extension est bien installée
ls ~/.local/share/gnome-shell/extensions/pilot-control@gnome-shell-extensions/
# 2. Vérifier qu'elle est activée
gnome-extensions list --enabled | grep pilot
# 3. Regarder les logs
journalctl --since "5 minutes ago" -o cat | grep -i pilot
# 4. Réinstaller l'extension
cd /home/gilles/app/pilot/gnome-pilot-extension
./install.sh
# 5. Redémarrer GNOME Shell (Alt+F2, 'r')
```
### Message "Extension had error"
```bash
# Voir les erreurs détaillées
journalctl -f -o cat /usr/bin/gnome-shell | grep -i pilot
# Ou utiliser Looking Glass (debugger GNOME)
# Alt+F2, tapez 'lg', regardez l'onglet "Errors"
```
### L'extension se charge mais rien ne se passe
```bash
# Vérifier que le service Pilot existe
systemctl --user status mqtt_pilot.service
# Vérifier que le config.yaml existe
cat ~/app/pilot/pilot-v2/config.yaml
# Tester manuellement l'extension
dbus-run-session -- gnome-shell --replace 2>&1 | grep -i pilot
```
## 📝 Actions courantes
### Activer/désactiver une métrique
1. Ouvrir le Control Panel
2. Section "Telemetry Metrics"
3. Utiliser le switch à côté de la métrique
4. Cliquer sur [💾] Save (en haut à droite)
5. Le service redémarre automatiquement
### Modifier l'intervalle d'une métrique
1. Ouvrir le Control Panel
2. Section "Telemetry Metrics"
3. Cliquer sur [...] à côté de la métrique
4. Modifier "Update Interval (seconds)"
5. Cliquer "Save" dans le dialogue
6. Cliquer [💾] Save dans la fenêtre principale
### Gérer les commandes autorisées
1. Ouvrir le Control Panel
2. Section "Commands"
3. Cliquer sur "Allowed Commands"
4. Cocher/décocher les commandes (shutdown, reboot, etc.)
5. Cliquer "Save" dans le dialogue
6. Cliquer [💾] Save dans la fenêtre principale
### Redémarrer le service Pilot
**Méthode rapide :**
1. Cliquer sur l'icône
2. Cliquer sur "Restart Service"
**Méthode complète :**
1. Ouvrir le Control Panel
2. Section "Service Control"
3. Cliquer sur le bouton "Restart"
## 🧪 Tester que tout fonctionne
```bash
# Script de test complet
cd /home/gilles/app/pilot/gnome-pilot-extension
./test.sh
# Vérifier manuellement
# 1. L'extension est activée
gnome-extensions list --enabled | grep pilot
# 2. Le service Pilot tourne
systemctl --user is-active mqtt_pilot.service
# 3. Le config.yaml est accessible
ls -l ~/app/pilot/pilot-v2/config.yaml
# 4. Les logs ne montrent pas d'erreur
journalctl --since "10 minutes ago" -o cat | grep -i "pilot.*error"
```
## 📖 Plus d'aide
- **README complet** : [README.md](README.md)
- **Guide débutant** : [GUIDE_DEBUTANT.md](GUIDE_DEBUTANT.md)
- **Architecture** : [STRUCTURE.md](STRUCTURE.md)
## 🎯 Résumé rapide
```bash
# 1. Redémarrer GNOME Shell (Alt+F2, 'r')
# 2. Chercher l'icône d'ordinateur en haut à droite
# 3. Cliquer dessus → "Open Control Panel"
# 4. Modifier vos paramètres
# 5. Cliquer sur [💾] Save
# 6. C'est tout ! Le service redémarre automatiquement
```
---
**Note importante :** L'extension a été corrigée pour supporter GNOME Shell 48. Elle devrait maintenant fonctionner correctement après un redémarrage de GNOME Shell.

View File

@@ -0,0 +1,433 @@
# Guide débutant - Extension Pilot Control pour GNOME
Ce guide explique en détail comment fonctionne l'extension et comment la personnaliser.
## 📚 Table des matières
1. [Architecture de l'extension](#architecture-de-lextension)
2. [Comprendre le code](#comprendre-le-code)
3. [Installation pas à pas](#installation-pas-à-pas)
4. [Personnalisation](#personnalisation)
5. [Dépannage](#dépannage)
---
## Architecture de l'extension
### Vue d'ensemble
Une extension GNOME Shell est composée de plusieurs fichiers JavaScript qui interagissent avec l'environnement GNOME :
```
Extension Pilot Control
├─ extension.js ──────────> Bouton dans le panel GNOME
│ │
│ └─> Menu déroulant (Start/Stop/Restart)
│ └─> Ouvre pilotWindow.js
├─ ui/pilotWindow.js ─────> Fenêtre principale
│ │
│ ├─> Section Services
│ ├─> Section Telemetry (avec métriques)
│ └─> Section Commands
├─ yamlConfig.js ─────────> Lecture/Écriture du config.yaml
└─ serviceManager.js ─────> Commandes systemctl (start/stop/restart)
```
### Fichiers principaux
| Fichier | Rôle | Difficulté |
|---------|------|------------|
| `metadata.json` | Infos sur l'extension (nom, version, UUID) | ⭐ Facile |
| `extension.js` | Point d'entrée, crée le bouton panel | ⭐⭐ Moyen |
| `yamlConfig.js` | Parse et sauvegarde le YAML | ⭐⭐⭐ Avancé |
| `serviceManager.js` | Contrôle systemd | ⭐⭐ Moyen |
| `ui/pilotWindow.js` | Interface principale | ⭐⭐⭐ Avancé |
| `ui/metricEditDialog.js` | Dialogue d'édition métrique | ⭐⭐ Moyen |
| `ui/commandEditDialog.js` | Dialogue d'édition commandes | ⭐⭐ Moyen |
| `prefs.js` | Fenêtre de préférences | ⭐ Facile |
---
## Comprendre le code
### 1. extension.js - Le point d'entrée
```javascript
// Ce fichier crée le bouton dans le panel GNOME
export default class PilotExtension extends Extension {
enable() {
// Appelé quand l'extension est activée
this._indicator = new PilotIndicator(this);
Main.panel.addToStatusArea(this.uuid, this._indicator);
}
disable() {
// Appelé quand l'extension est désactivée
this._indicator.destroy();
}
}
```
**Concepts clés :**
- `enable()` : Fonction appelée au démarrage de l'extension
- `disable()` : Fonction appelée à l'arrêt de l'extension
- `PilotIndicator` : Classe qui crée l'icône + menu dans le panel
### 2. yamlConfig.js - Parser YAML simple
```javascript
// Ce fichier lit et écrit le fichier config.yaml
export class YamlConfig {
load() {
// 1. Lit le fichier config.yaml
// 2. Parse le YAML en objet JavaScript
// 3. Retourne l'objet config
}
save(config) {
// 1. Convertit l'objet JavaScript en YAML
// 2. Crée une sauvegarde du fichier actuel
// 3. Écrit le nouveau fichier
}
}
```
**Fonctions utiles :**
- `getTelemetryMetrics()` : Retourne toutes les métriques
- `getCommandsAllowlist()` : Retourne la liste des commandes autorisées
- `updateTelemetryMetric(name, updates)` : Modifie une métrique
- `setTelemetryEnabled(enabled)` : Active/désactive la télémétrie
### 3. serviceManager.js - Contrôle systemd
```javascript
// Ce fichier exécute les commandes systemctl
export class ServiceManager {
startService() {
// Exécute: systemctl --user start mqtt_pilot.service
}
stopService() {
// Exécute: systemctl --user stop mqtt_pilot.service
}
isServiceActive() {
// Vérifie si le service est en cours d'exécution
}
}
```
**Commandes systemctl utilisées :**
- `systemctl --user start` : Démarrer le service
- `systemctl --user stop` : Arrêter le service
- `systemctl --user restart` : Redémarrer le service
- `systemctl --user is-active` : Vérifier le statut
### 4. ui/pilotWindow.js - Interface graphique
```javascript
// Fenêtre principale avec GTK4 + libadwaita
export const PilotWindow = GObject.registerClass(
class PilotWindow extends Adw.Window {
_buildUI() {
// Construit 3 sections :
// 1. Service Control
// 2. Telemetry Metrics
// 3. Commands
}
_loadData() {
// Charge les données depuis config.yaml
// Met à jour l'interface
}
_saveConfig() {
// Sauvegarde les modifications
// Redémarre le service
}
});
```
**Composants UI utilisés :**
- `Adw.Window` : Fenêtre principale
- `Adw.HeaderBar` : Barre d'en-tête avec boutons
- `Adw.PreferencesGroup` : Groupes de préférences
- `Adw.ActionRow` : Lignes avec titre/sous-titre
- `Gtk.Switch` : Interrupteur ON/OFF
- `Gtk.Button` : Boutons
---
## Installation pas à pas
### Étape 1 : Vérifier les prérequis
```bash
# Vérifier que GNOME Shell est installé
gnome-shell --version
# Doit afficher : GNOME Shell 45.x ou supérieur
# Vérifier que le service Pilot existe
systemctl --user status mqtt_pilot.service
```
### Étape 2 : Installer l'extension
**Méthode automatique (recommandée) :**
```bash
cd /home/gilles/app/pilot/gnome-pilot-extension
./install.sh
```
**Méthode manuelle :**
```bash
# 1. Créer le répertoire
mkdir -p ~/.local/share/gnome-shell/extensions/pilot-control@gnome-shell-extensions
# 2. Copier tous les fichiers
cp -r /home/gilles/app/pilot/gnome-pilot-extension/* \
~/.local/share/gnome-shell/extensions/pilot-control@gnome-shell-extensions/
# 3. Vérifier que les fichiers sont bien copiés
ls ~/.local/share/gnome-shell/extensions/pilot-control@gnome-shell-extensions/
```
### Étape 3 : Activer l'extension
```bash
# Activer l'extension
gnome-extensions enable pilot-control@gnome-shell-extensions
# Vérifier qu'elle est bien activée
gnome-extensions list --enabled | grep pilot
```
### Étape 4 : Redémarrer GNOME Shell
**Sur X11 :**
1. Appuyez sur `Alt+F2`
2. Tapez `r`
3. Appuyez sur `Entrée`
**Sur Wayland :**
1. Déconnectez-vous
2. Reconnectez-vous
### Étape 5 : Utiliser l'extension
1. Cherchez l'icône d'ordinateur en haut à droite (dans le panel)
2. Cliquez dessus pour voir le menu
3. Cliquez sur "Open Control Panel" pour ouvrir la fenêtre principale
---
## Personnalisation
### Modifier le chemin du fichier de configuration
Éditez [yamlConfig.js:25-32](yamlConfig.js#L25-L32) :
```javascript
_findConfigPath() {
const possiblePaths = [
'/votre/chemin/personnalisé/config.yaml', // Ajoutez votre chemin ici
GLib.build_filenamev([GLib.get_home_dir(), 'app/pilot/pilot-v2/config.yaml']),
// ... autres chemins
];
}
```
### Modifier le nom du service systemd
Éditez [serviceManager.js:9](serviceManager.js#L9) :
```javascript
constructor() {
this.serviceName = 'votre_service.service'; // Changez ici
}
```
### Changer l'icône du panel
Éditez [extension.js:40-43](extension.js#L40-L43) :
```javascript
const icon = new St.Icon({
icon_name: 'network-server-symbolic', // Changez l'icône ici
style_class: 'system-status-icon',
});
```
**Icônes disponibles :**
- `computer-symbolic`
- `network-server-symbolic`
- `preferences-system-symbolic`
- `system-run-symbolic`
### Ajouter une nouvelle métrique
1. Ajoutez la métrique dans `config.yaml` (Pilot V2)
2. L'extension détectera automatiquement la nouvelle métrique
3. Elle apparaîtra dans la section Telemetry
### Personnaliser les couleurs
Éditez [stylesheet.css](stylesheet.css) :
```css
/* Changer la couleur des warnings */
.warning {
color: #ff0000; /* Rouge au lieu d'orange */
}
/* Ajouter des styles personnalisés */
.custom-style {
background-color: #3584e4;
color: white;
}
```
---
## Dépannage
### L'extension n'apparaît pas dans la liste
```bash
# Vérifier que l'extension est bien installée
ls -la ~/.local/share/gnome-shell/extensions/pilot-control@gnome-shell-extensions/
# Vérifier les logs GNOME Shell
journalctl -f -o cat /usr/bin/gnome-shell | grep -i pilot
```
**Solution :** Vérifiez que tous les fichiers sont bien copiés, notamment `metadata.json`.
### Erreur "Extension had error"
```bash
# Voir les erreurs détaillées
journalctl -f -o cat /usr/bin/gnome-shell
```
**Solutions courantes :**
1. Vérifier la syntaxe JavaScript (pas de fautes de frappe)
2. Vérifier que tous les imports sont corrects
3. Redémarrer GNOME Shell
### Le service ne démarre pas
```bash
# Vérifier que le service existe
systemctl --user list-units | grep mqtt_pilot
# Vérifier les erreurs du service
systemctl --user status mqtt_pilot.service
journalctl --user -u mqtt_pilot.service -n 50
```
**Solution :** Assurez-vous que le service `mqtt_pilot.service` est bien configuré en tant que service utilisateur.
### Les modifications ne sont pas sauvegardées
1. Vérifiez les permissions du fichier `config.yaml`
2. Vérifiez les logs pour voir les erreurs de sauvegarde
3. Vérifiez qu'une sauvegarde a été créée (`config.yaml.backup_*`)
```bash
# Vérifier les permissions
ls -la ~/app/pilot/pilot-v2/config.yaml
# Vérifier les sauvegardes
ls -la ~/app/pilot/pilot-v2/config.yaml.backup_*
```
### L'interface ne se met pas à jour
1. Cliquez sur le bouton "Refresh" (icône refresh dans le header)
2. Fermez et rouvrez la fenêtre de contrôle
3. Redémarrez l'extension :
```bash
gnome-extensions disable pilot-control@gnome-shell-extensions
gnome-extensions enable pilot-control@gnome-shell-extensions
```
---
## Ressources utiles
### Documentation GNOME
- [GNOME Shell Extensions](https://gjs.guide/extensions/)
- [GJS Guide](https://gjs.guide/)
- [GTK4 Documentation](https://docs.gtk.org/gtk4/)
- [Libadwaita Documentation](https://gnome.pages.gitlab.gnome.org/libadwaita/)
### Outils de développement
```bash
# Looking Glass (debugger GNOME Shell)
# Alt+F2, tapez 'lg'
# Recharger l'extension rapidement
gnome-extensions disable pilot-control@gnome-shell-extensions && \
gnome-extensions enable pilot-control@gnome-shell-extensions
# Voir les logs en temps réel
journalctl -f -o cat /usr/bin/gnome-shell
```
### Exemples de code
L'extension utilise plusieurs patterns courants :
**Pattern 1 : GObject.registerClass**
```javascript
const MyClass = GObject.registerClass(
class MyClass extends ParentClass {
_init() {
super._init();
// Initialisation
}
});
```
**Pattern 2 : Callbacks (connect)**
```javascript
button.connect('clicked', () => {
// Action au clic
});
```
**Pattern 3 : Timeout**
```javascript
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
// Exécuté après 500ms
return GLib.SOURCE_REMOVE; // Ne pas répéter
});
```
---
## Conclusion
Cette extension est conçue pour être simple et facilement modifiable. N'hésitez pas à :
1. **Expérimenter** : Modifier les fichiers et tester
2. **Consulter les logs** : Utiliser `journalctl` pour comprendre les erreurs
3. **Lire le code** : Tous les fichiers sont commentés pour faciliter la compréhension
Pour toute question, consultez d'abord ce guide, puis les logs GNOME Shell.
Bon développement ! 🚀

View File

@@ -0,0 +1,254 @@
# Pilot Control - GNOME Shell Extension
Extension GNOME Shell pour contrôler Pilot V2 (agent MQTT pour Home Assistant) directement depuis votre environnement de bureau.
## Fonctionnalités
- **Contrôle du service systemd** : Démarrer, arrêter, redémarrer le service Pilot V2
- **Gestion de la télémétrie** : Activer/désactiver les métriques système individuellement
- **Configuration des commandes** : Gérer la liste des commandes MQTT autorisées
- **Édition simplifiée** : Modifier les paramètres sans éditer manuellement le YAML
- **Interface intuitive** : Icône dans le panel GNOME avec menu rapide et fenêtre de contrôle complète
## Prérequis
- GNOME Shell 45 ou supérieur
- Pilot V2 installé et configuré
- Service systemd `mqtt_pilot.service` (user service)
- GTK4 et libadwaita
## Structure des fichiers
```
gnome-pilot-extension/
├── metadata.json # Métadonnées de l'extension
├── extension.js # Point d'entrée principal
├── prefs.js # Fenêtre de préférences
├── yamlConfig.js # Module de lecture/écriture YAML
├── serviceManager.js # Gestion du service systemd
├── stylesheet.css # Styles CSS
├── ui/
│ ├── pilotWindow.js # Fenêtre principale
│ ├── metricEditDialog.js # Dialogue d'édition des métriques
│ └── commandEditDialog.js # Dialogue d'édition des commandes
└── README.md
```
## Installation
### Méthode 1 : Installation manuelle
1. **Copier l'extension dans le répertoire des extensions utilisateur** :
```bash
# Depuis le répertoire du projet Pilot
mkdir -p ~/.local/share/gnome-shell/extensions/pilot-control@gnome-shell-extensions
cp -r gnome-pilot-extension/* ~/.local/share/gnome-shell/extensions/pilot-control@gnome-shell-extensions/
```
2. **Redémarrer GNOME Shell** :
- Sur X11 : `Alt+F2`, taper `r`, puis `Entrée`
- Sur Wayland : Déconnexion/reconnexion
3. **Activer l'extension** :
```bash
gnome-extensions enable pilot-control@gnome-shell-extensions
```
Ou via l'application Extensions (GNOME Extensions).
### Méthode 2 : Script d'installation automatique
Créez un script d'installation :
```bash
#!/bin/bash
# install-extension.sh
EXTENSION_UUID="pilot-control@gnome-shell-extensions"
EXTENSION_DIR="$HOME/.local/share/gnome-shell/extensions/$EXTENSION_UUID"
echo "Installing Pilot Control extension..."
# Créer le répertoire
mkdir -p "$EXTENSION_DIR"
# Copier les fichiers
cp -r gnome-pilot-extension/* "$EXTENSION_DIR/"
# Vérifier l'installation
if [ -f "$EXTENSION_DIR/metadata.json" ]; then
echo "✓ Extension installed successfully"
echo " Location: $EXTENSION_DIR"
echo ""
echo "Next steps:"
echo "1. Restart GNOME Shell (Alt+F2, type 'r', Enter on X11)"
echo "2. Enable the extension:"
echo " gnome-extensions enable $EXTENSION_UUID"
else
echo "✗ Installation failed"
exit 1
fi
```
Rendre le script exécutable et l'exécuter :
```bash
chmod +x install-extension.sh
./install-extension.sh
```
## Utilisation
### Accès rapide depuis le panel
1. Cliquez sur l'icône d'ordinateur dans le panel GNOME (en haut à droite)
2. Le menu affiche :
- **Status du service** (Running/Stopped)
- **Actions rapides** : Start, Stop, Restart
- **Open Control Panel** : Ouvre la fenêtre complète
### Fenêtre de contrôle principale
La fenêtre principale est divisée en 3 sections :
#### 1. Service Control
- **Service Status** : Switch pour démarrer/arrêter le service
- **Auto-start Service** : Enable/disable au démarrage du système
- **Restart Service** : Redémarrer pour appliquer les changements
#### 2. Telemetry Metrics
- **Enable Telemetry** : Switch global pour toute la télémétrie
- Liste des métriques individuelles :
- Switch pour activer/désactiver chaque métrique
- Bouton "..." pour éditer les paramètres (nom, interval, etc.)
#### 3. Commands
- **Enable Commands** : Switch global pour les commandes MQTT
- **Allowed Commands** : Gérer la liste des commandes autorisées
- shutdown, reboot, sleep, hibernate, screen
### Édition des métriques
Cliquez sur le bouton "..." à côté d'une métrique pour modifier :
- **Enabled** : Activer/désactiver
- **Display Name** : Nom affiché dans Home Assistant
- **Unique ID** : Identifiant unique
- **Update Interval** : Intervalle de mise à jour (en secondes)
### Édition des commandes autorisées
Cliquez sur "Allowed Commands" pour :
- Sélectionner les commandes à autoriser via MQTT
- Voir la description de chaque commande
- Sauvegarder les modifications
## Sauvegarde et rechargement
- **Sauvegarde automatique** : Lors de chaque modification, une sauvegarde du config.yaml est créée (config.yaml.backup_TIMESTAMP)
- **Rechargement du service** : Après sauvegarde, le service est automatiquement redémarré pour appliquer les changements
- **Bouton Refresh** : Recharge la configuration depuis le fichier YAML
## Configuration
Par défaut, l'extension cherche le fichier de configuration dans :
1. `~/app/pilot/pilot-v2/config.yaml`
2. `~/.config/pilot/config.yaml`
3. `/etc/pilot/config.yaml`
4. `./pilot-v2/config.yaml`
Pour modifier le chemin, utilisez les préférences de l'extension.
## Dépannage
### L'extension ne se charge pas
```bash
# Vérifier les logs
journalctl -f -o cat /usr/bin/gnome-shell
# Vérifier que l'extension est bien installée
gnome-extensions list | grep pilot
# Vérifier les erreurs
gnome-extensions show pilot-control@gnome-shell-extensions
```
### Le service ne démarre pas
```bash
# Vérifier le status du service
systemctl --user status mqtt_pilot.service
# Vérifier les logs
journalctl --user -u mqtt_pilot.service -n 50
```
### Problèmes de permissions
Si les commandes systemctl ne fonctionnent pas, vérifiez que le service est bien un service utilisateur (user service) et non un service système.
### Fichier config.yaml non trouvé
Modifiez le chemin dans [yamlConfig.js:25-32](gnome-pilot-extension/yamlConfig.js#L25-L32) ou utilisez les préférences.
## Développement
### Tester les modifications
```bash
# Recharger l'extension après modifications
gnome-extensions disable pilot-control@gnome-shell-extensions
gnome-extensions enable pilot-control@gnome-shell-extensions
# Sur X11, recharger GNOME Shell
# Alt+F2, 'r', Entrée
```
### Consulter les logs
```bash
# Logs en temps réel
journalctl -f -o cat /usr/bin/gnome-shell | grep -i pilot
# Ou via Looking Glass (Alt+F2, 'lg')
```
### Structure du code
- **extension.js** : Crée le bouton panel et gère le cycle de vie
- **ui/pilotWindow.js** : Fenêtre principale avec 3 sections
- **yamlConfig.js** : Parser/writer YAML simple
- **serviceManager.js** : Wrapper systemctl
- **ui/*Dialog.js** : Dialogues d'édition
## Limitations connues (V1)
- Parser YAML simple (ne gère que la structure de config.yaml)
- Pas de validation avancée des entrées
- Pas de support pour les configurations complexes (listes imbriquées, etc.)
- Nécessite un redémarrage du service pour appliquer les changements
## Améliorations futures
- Support de plusieurs instances de Pilot
- Gestion des logs en temps réel
- Notifications pour les événements importants
- Support des thèmes sombres/clairs
- Validation avancée des entrées
- Backup/restore de configurations
## Licence
Même licence que le projet Pilot
## Auteur
Extension créée pour simplifier la gestion de Pilot V2 depuis GNOME Shell

View File

@@ -0,0 +1,357 @@
# Résumé du Projet - Extension Pilot Control
## 🎯 Objectif
Créer une extension GNOME Shell simple pour piloter l'application Pilot V2 (agent MQTT pour Home Assistant) directement depuis le bureau GNOME, sans avoir à éditer manuellement le fichier `config.yaml`.
## ✅ Fonctionnalités implémentées (V1)
### 1. Contrôle du service systemd
- ✅ Démarrer/Arrêter le service `mqtt_pilot.service`
- ✅ Redémarrer le service (pour appliquer les changements)
- ✅ Activer/Désactiver le démarrage automatique
- ✅ Affichage du statut en temps réel (Running/Stopped)
### 2. Gestion de la télémétrie
- ✅ Switch global pour activer/désactiver toute la télémétrie
- ✅ Liste des métriques disponibles (CPU, Memory, Battery, etc.)
- ✅ Switch individuel pour chaque métrique
- ✅ Dialogue d'édition pour modifier :
- Enabled (activé/désactivé)
- Display Name (nom affiché)
- Unique ID (identifiant unique)
- Update Interval (intervalle en secondes)
### 3. Gestion des commandes
- ✅ Switch global pour activer/désactiver les commandes
- ✅ Éditeur de la allowlist (liste des commandes autorisées)
- ✅ Sélection des commandes : shutdown, reboot, sleep, hibernate, screen
- ✅ Descriptions des commandes pour chaque action
### 4. Interface utilisateur
- ✅ Icône dans le panel GNOME (top bar)
- ✅ Menu déroulant avec actions rapides
- ✅ Fenêtre principale avec 3 sections (Services, Telemetry, Commands)
- ✅ Interface moderne avec GTK4 + Libadwaita
- ✅ Boutons Refresh et Save dans le header
### 5. Gestion de la configuration
- ✅ Lecture du fichier `config.yaml` (parser YAML simple)
- ✅ Écriture des modifications dans le fichier
- ✅ Création automatique de backups (config.yaml.backup_timestamp)
- ✅ Rechargement automatique du service après sauvegarde
## 📁 Fichiers créés
```
gnome-pilot-extension/
├── metadata.json (140 lignes)
├── extension.js (150 lignes)
├── prefs.js (80 lignes)
├── yamlConfig.js (290 lignes)
├── serviceManager.js (150 lignes)
├── stylesheet.css (20 lignes)
├── ui/
│ ├── pilotWindow.js (400 lignes)
│ ├── metricEditDialog.js (130 lignes)
│ └── commandEditDialog.js (140 lignes)
├── install.sh (100 lignes)
├── README.md (350 lignes)
├── GUIDE_DEBUTANT.md (600 lignes)
├── STRUCTURE.md (400 lignes)
└── RESUME_PROJET.md (ce fichier)
Total: ~2950 lignes de code et documentation
```
## 🛠️ Technologies utilisées
- **GJS** : JavaScript runtime pour GNOME
- **GTK4** : Toolkit d'interface graphique
- **Libadwaita** : Composants UI modernes GNOME
- **GLib/Gio** : Utilitaires système (fichiers, processus)
- **GNOME Shell 45+** : API d'extension
## 🚀 Installation
### Méthode rapide (recommandée)
```bash
cd /home/gilles/app/pilot/gnome-pilot-extension
./install.sh
```
### Méthode manuelle
```bash
# Copier l'extension
mkdir -p ~/.local/share/gnome-shell/extensions/pilot-control@gnome-shell-extensions
cp -r /home/gilles/app/pilot/gnome-pilot-extension/* \
~/.local/share/gnome-shell/extensions/pilot-control@gnome-shell-extensions/
# Activer l'extension
gnome-extensions enable pilot-control@gnome-shell-extensions
# Redémarrer GNOME Shell (X11: Alt+F2, 'r' | Wayland: logout/login)
```
## 📖 Documentation
| Fichier | Description |
|---------|-------------|
| **README.md** | Documentation complète (fonctionnalités, installation, usage) |
| **GUIDE_DEBUTANT.md** | Guide détaillé pour comprendre le code (débutants) |
| **STRUCTURE.md** | Architecture, diagrammes, flux de données |
| **RESUME_PROJET.md** | Ce fichier (vue d'ensemble du projet) |
## 🎨 Captures d'écran conceptuelles
### Panel menu
```
┌─────────────────────────┐
│ [Icon] Pilot Control ▼ │
│ ├─ Status: 🟢 Running │
│ ├─ Start Service │
│ ├─ Stop Service │
│ ├─ Restart Service │
│ ├─ Open Control Panel │
│ └─ Reload Config │
└─────────────────────────┘
```
### Fenêtre principale
```
┌──────────────────────────────────────┐
│ Pilot Control Panel [↻] [💾] │
├──────────────────────────────────────┤
│ ┌─ Service Control ────────────────┐ │
│ │ Service Status [Switch ON] │ │
│ │ Auto-start [Switch ON] │ │
│ │ Restart Service [Button] │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌─ Telemetry Metrics ──────────────┐ │
│ │ Enable Telemetry [Switch ON] │ │
│ │ │ │
│ │ CPU Usage [ON] [Edit...] │ │
│ │ Memory [ON] [Edit...] │ │
│ │ Battery [ON] [Edit...] │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌─ Commands ───────────────────────┐ │
│ │ Enable Commands [Switch ON] │ │
│ │ Allowed Commands [Edit...] │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────┘
```
## 🔄 Workflow d'utilisation
### Scénario 1 : Activer/désactiver une métrique
1. Cliquer sur l'icône dans le panel
2. Cliquer sur "Open Control Panel"
3. Dans la section "Telemetry Metrics", utiliser le switch de la métrique
4. Cliquer sur [💾] Save
5. Le service redémarre automatiquement
6. La métrique est activée/désactivée dans Pilot V2
### Scénario 2 : Modifier l'intervalle d'une métrique
1. Ouvrir le Control Panel
2. Cliquer sur [Edit...] à côté de la métrique
3. Modifier "Update Interval (seconds)"
4. Cliquer "Save"
5. Fermer le dialogue
6. Cliquer sur [💾] Save dans la fenêtre principale
7. Le service redémarre et applique le nouvel intervalle
### Scénario 3 : Gérer la allowlist des commandes
1. Ouvrir le Control Panel
2. Dans "Commands", cliquer sur "Allowed Commands"
3. Cocher/décocher les commandes souhaitées
4. Cliquer "Save"
5. Cliquer sur [💾] Save dans la fenêtre principale
6. Le service redémarre avec la nouvelle allowlist
### Scénario 4 : Redémarrer le service rapidement
**Option 1 (menu rapide)** :
1. Cliquer sur l'icône dans le panel
2. Cliquer sur "Restart Service"
**Option 2 (fenêtre complète)** :
1. Ouvrir le Control Panel
2. Dans "Service Control", cliquer sur "Restart"
## 🔐 Sécurité
### Bonnes pratiques implémentées
1. **Backups automatiques** : Chaque modification crée une sauvegarde
2. **Service utilisateur uniquement** : Utilise `systemctl --user` (pas de sudo)
3. **Validation basique** : Vérification des entrées utilisateur
4. **Warnings** : Avertissements pour les commandes système critiques
### Limites de sécurité
- L'extension peut modifier le config.yaml de l'utilisateur
- L'extension peut contrôler le service systemd de l'utilisateur
- Pas de validation avancée des commandes (V1)
## 📊 Statistiques du projet
- **Fichiers JavaScript** : 8 fichiers
- **Lignes de code** : ~1500 lignes
- **Lignes de documentation** : ~1450 lignes
- **Ratio doc/code** : ~1:1 (excellent pour un projet pédagogique)
- **Temps de développement estimé** : 6-8 heures pour un développeur expérimenté
## 🧪 Tests recommandés
### Tests de base
1. ✅ Installation de l'extension
2. ✅ Activation de l'extension
3. ✅ Apparition de l'icône dans le panel
4. ✅ Ouverture du menu déroulant
5. ✅ Ouverture de la fenêtre principale
### Tests fonctionnels
1. ✅ Lecture du config.yaml
2. ✅ Affichage des métriques existantes
3. ✅ Modification d'une métrique
4. ✅ Sauvegarde du config.yaml
5. ✅ Création du backup
6. ✅ Redémarrage du service
### Tests de contrôle du service
1. ✅ Vérification du statut
2. ✅ Démarrage du service
3. ✅ Arrêt du service
4. ✅ Redémarrage du service
### Commandes de test
```bash
# Test 1: Vérifier que l'extension est installée
gnome-extensions list | grep pilot
# Test 2: Vérifier le config.yaml
cat ~/app/pilot/pilot-v2/config.yaml
# Test 3: Vérifier les backups créés
ls -lh ~/app/pilot/pilot-v2/config.yaml.backup_*
# Test 4: Vérifier le statut du service
systemctl --user status mqtt_pilot.service
# Test 5: Voir les logs de l'extension
journalctl -f -o cat /usr/bin/gnome-shell | grep -i pilot
```
## 🐛 Problèmes connus et solutions
### Problème 1 : Extension ne se charge pas
**Symptômes** : L'icône n'apparaît pas dans le panel
**Solutions** :
1. Vérifier que GNOME Shell >= 45
2. Vérifier les logs : `journalctl -f -o cat /usr/bin/gnome-shell`
3. Redémarrer GNOME Shell
4. Réinstaller l'extension
### Problème 2 : Config.yaml non trouvé
**Symptômes** : Erreur "Failed to load configuration"
**Solutions** :
1. Vérifier le chemin dans `yamlConfig.js:_findConfigPath()`
2. Créer le fichier manuellement
3. Modifier les permissions : `chmod 644 config.yaml`
### Problème 3 : Service ne redémarre pas
**Symptômes** : Les modifications ne sont pas appliquées
**Solutions** :
1. Vérifier que le service existe : `systemctl --user list-units | grep mqtt_pilot`
2. Redémarrer manuellement : `systemctl --user restart mqtt_pilot.service`
3. Vérifier les logs : `journalctl --user -u mqtt_pilot.service`
## 🎓 Points d'apprentissage
Ce projet permet d'apprendre :
1. **Architecture GNOME Extensions** : Structure, métadonnées, cycle de vie
2. **GTK4 + Libadwaita** : Composants UI modernes
3. **GJS** : JavaScript pour GNOME (imports, GObject)
4. **Gestion de fichiers** : Lecture/écriture avec Gio
5. **Interaction avec systemd** : Commandes systemctl
6. **Parser YAML** : Implémentation d'un parser simple
7. **Patterns UI** : Dialogues, switches, action rows
## 🚧 Améliorations possibles (V2+)
### Fonctionnalités
- [ ] Support de plusieurs instances de Pilot
- [ ] Visualisation des logs en temps réel
- [ ] Notifications pour les événements (service stopped, etc.)
- [ ] Graphiques de métriques (historique)
- [ ] Export/import de configurations
- [ ] Validation avancée des entrées (regex, ranges)
### Technique
- [ ] Parser YAML complet (librairie externe ?)
- [ ] Support des configurations complexes
- [ ] Validation des entrées utilisateur
- [ ] Tests automatisés (unit tests)
- [ ] CI/CD pour les releases
- [ ] Support de GSettings pour les préférences
### UI/UX
- [ ] Thèmes sombres/clairs personnalisés
- [ ] Animations et transitions
- [ ] Tooltips explicatifs
- [ ] Raccourcis clavier
- [ ] Drag & drop pour réordonner les métriques
## 📝 Conclusion
### Objectifs atteints
**Extension fonctionnelle** pour contrôler Pilot V2 depuis GNOME Shell
**Interface intuitive** avec GTK4/Libadwaita
**Code commenté** pour débutants
**Documentation complète** (README, Guide, Structure)
**Installation simple** (script automatique)
**Sauvegarde automatique** des configurations
**Rechargement du service** après modification
### Utilisation recommandée
Cette extension est parfaite pour :
- Les utilisateurs de Pilot V2 sur GNOME
- Éviter l'édition manuelle du config.yaml
- Contrôler rapidement le service systemd
- Activer/désactiver des fonctionnalités à la volée
### Prochaines étapes
1. **Tester l'extension** : Installer et utiliser
2. **Rapporter les bugs** : Créer des issues si nécessaire
3. **Proposer des améliorations** : Pull requests bienvenues
4. **Partager** : Publier sur extensions.gnome.org (optionnel)
---
**Développé pour Pilot V2**
**Compatible GNOME Shell 45+**
**Version 1.0 - Décembre 2025**

View File

@@ -0,0 +1,307 @@
# Structure de l'extension Pilot Control
## Arborescence des fichiers
```
gnome-pilot-extension/
├── metadata.json # Métadonnées de l'extension (UUID, version, etc.)
├── extension.js # Point d'entrée principal (bouton panel + menu)
├── prefs.js # Fenêtre de préférences (optionnel V1)
├── yamlConfig.js # Module de lecture/écriture YAML
├── serviceManager.js # Gestion du service systemd
├── stylesheet.css # Styles CSS personnalisés
├── ui/ # Composants d'interface utilisateur
│ ├── pilotWindow.js # Fenêtre principale (3 sections)
│ ├── metricEditDialog.js # Dialogue d'édition des métriques
│ └── commandEditDialog.js # Dialogue d'édition des commandes
├── schemas/ # GSettings schema (optionnel, vide pour V1)
├── install.sh # Script d'installation automatique
├── README.md # Documentation complète
├── GUIDE_DEBUTANT.md # Guide pour débutants
└── STRUCTURE.md # Ce fichier (vue d'ensemble)
```
## Diagramme de flux
```
┌─────────────────────────────────────────────────────────────┐
│ GNOME Shell Panel │
│ │
│ [Icon] Pilot Control ▼ │
│ ├─ Status: 🟢 Running │
│ ├─ Start Service │
│ ├─ Stop Service │
│ ├─ Restart Service │
│ ├─ Open Control Panel ──────────┐ │
│ └─ Reload Config │ │
└──────────────────────────────────────────┼──────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Pilot Control Panel │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ [Refresh] [Save] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Service Control ──────────────────────────────────────┐ │
│ │ Service Status [Switch ON/OFF] │ │
│ │ Auto-start Service [Switch ON/OFF] │ │
│ │ Restart Service [Button: Restart] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Telemetry Metrics ────────────────────────────────────┐ │
│ │ Enable Telemetry [Switch ON/OFF] │ │
│ │ │ │
│ │ CPU Usage [Switch] [Edit...] │ │
│ │ Memory Usage [Switch] [Edit...] │ │
│ │ Battery [Switch] [Edit...] │ │
│ │ Temperature [Switch] [Edit...] │ │
│ │ IP Address [Switch] [Edit...] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Commands ─────────────────────────────────────────────┐ │
│ │ Enable Commands [Switch ON/OFF] │ │
│ │ Allowed Commands [Edit...] │ │
│ │ → shutdown, reboot, sleep, hibernate, screen │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
## Flux de données
```
┌──────────────┐
│ User │
│ Action │
└──────┬───────┘
┌──────────────────────────────────────────┐
│ extension.js │
│ (PilotIndicator: Panel Button + Menu) │
└──────┬───────────────────────────────────┘
├─────────────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Service │ │ ui/ │
│ Manager │ │ pilotWindow │
└──────┬───────┘ └──────┬───────┘
│ │
│ ▼
│ ┌──────────────┐
│ │ yamlConfig │
│ │ (Parser) │
│ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ systemctl │ │ config.yaml │
│ commands │ │ (Pilot V2) │
└──────────────┘ └──────────────┘
│ │
└─────────┬───────────┘
┌──────────────┐
│ mqtt_pilot │
│ .service │
│ (Pilot V2) │
└──────────────┘
```
## Interactions entre modules
### 1. extension.js → serviceManager.js
```javascript
// Contrôle du service systemd
serviceManager.startService()
serviceManager.stopService()
serviceManager.restartService()
serviceManager.isServiceActive()
```
### 2. extension.js → ui/pilotWindow.js
```javascript
// Ouvre la fenêtre principale
new PilotWindow(extension, yamlConfig, serviceManager)
window.show()
```
### 3. ui/pilotWindow.js → yamlConfig.js
```javascript
// Lecture de la config
yamlConfig.load()
yamlConfig.getTelemetryMetrics()
yamlConfig.getCommandsAllowlist()
// Modification de la config
yamlConfig.updateTelemetryMetric(name, updates)
yamlConfig.setTelemetryEnabled(enabled)
yamlConfig.updateCommandsAllowlist(newList)
// Sauvegarde
yamlConfig.save()
```
### 4. ui/pilotWindow.js → ui/metricEditDialog.js
```javascript
// Édition d'une métrique
const dialog = new MetricEditDialog(parent, metricName, config)
dialog.connect('response', (dlg, responseId) => {
const updates = dialog.getUpdates()
// Appliquer les modifications
})
```
### 5. ui/pilotWindow.js → ui/commandEditDialog.js
```javascript
// Édition de la allowlist
const dialog = new CommandEditDialog(parent, currentAllowlist)
dialog.connect('response', (dlg, responseId) => {
const newAllowlist = dialog.getAllowlist()
// Appliquer les modifications
})
```
## Cycle de vie de l'extension
```
1. GNOME Shell démarre
2. Extension activée (enable())
3. PilotIndicator créé
4. Icône ajoutée au panel
5. Menu construit
6. Config chargée (yamlConfig.load())
7. Status du service vérifié
8. Extension prête
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
User clique sur "Open Control Panel"
PilotWindow créée
Sections construites (Service, Telemetry, Commands)
Données chargées depuis config.yaml
Interface affichée
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
User modifie un paramètre
Config marquée comme "dirty"
User clique "Save"
Backup créé (config.yaml.backup_timestamp)
Nouveau config.yaml écrit
Service redémarré (systemctl restart)
Interface mise à jour
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Extension désactivée (disable())
Fenêtre détruite
Indicateur supprimé du panel
Extension arrêtée
```
## Technologies utilisées
| Technologie | Version | Usage |
|-------------|---------|-------|
| **GJS** | - | JavaScript runtime pour GNOME |
| **GTK4** | 4.x | Toolkit d'interface graphique |
| **Libadwaita** | 1.x | Composants UI modernes GNOME |
| **GLib** | 2.x | Utilitaires système (fichiers, processus) |
| **Gio** | 2.x | I/O (lecture/écriture fichiers) |
| **GNOME Shell** | 45+ | Environnement de bureau |
## Dépendances externes
L'extension **n'a pas** de dépendances externes npm/node. Tout est fourni par :
- GNOME Shell
- GJS (inclus avec GNOME)
- GTK4 et Libadwaita (librairies système)
## Permissions requises
L'extension nécessite :
1. **Accès fichiers** :
- Lecture : `~/app/pilot/pilot-v2/config.yaml`
- Écriture : `~/app/pilot/pilot-v2/config.yaml`
- Création de backups : `~/app/pilot/pilot-v2/config.yaml.backup_*`
2. **Commandes systemctl** :
- `systemctl --user start mqtt_pilot.service`
- `systemctl --user stop mqtt_pilot.service`
- `systemctl --user restart mqtt_pilot.service`
- `systemctl --user status mqtt_pilot.service`
- `systemctl --user is-active mqtt_pilot.service`
- `systemctl --user enable mqtt_pilot.service`
- `systemctl --user disable mqtt_pilot.service`
3. **Logs** :
- `journalctl --user -u mqtt_pilot.service`
## Sécurité
### Ce que l'extension PEUT faire :
- ✅ Lire et modifier le fichier config.yaml de l'utilisateur
- ✅ Contrôler le service systemd de l'utilisateur (--user)
- ✅ Créer des sauvegardes des fichiers de configuration
### Ce que l'extension NE PEUT PAS faire :
- ❌ Accéder aux services système (nécessite sudo)
- ❌ Modifier des fichiers en dehors du home directory
- ❌ Exécuter des commandes arbitraires non validées
- ❌ Accéder au réseau (pas d'API MQTT directe)
## Limites de V1
1. **Parser YAML simple** : Ne gère que la structure de config.yaml de Pilot V2
2. **Pas de validation avancée** : Entrées utilisateur validées basiquement
3. **Rechargement par redémarrage** : Nécessite un restart du service (pas de reload à chaud)
4. **Une seule instance** : Gère uniquement un service Pilot à la fois
5. **Pas de logs temps réel** : Pas d'affichage des logs dans l'interface
## Extensions futures possibles
- Support de plusieurs instances de Pilot
- Visualisation des logs en temps réel
- Notifications pour événements importants
- Graphiques de métriques (historique)
- Export/import de configurations
- Validation avancée des entrées (regex, ranges)
- Thèmes sombres/clairs personnalisés
- Support de configurations complexes (YAML avancé)
## Ressources
- [Documentation GNOME Extensions](https://gjs.guide/extensions/)
- [GTK4 Documentation](https://docs.gtk.org/gtk4/)
- [Libadwaita Documentation](https://gnome.pages.gitlab.gnome.org/libadwaita/)
- [GJS Examples](https://gitlab.gnome.org/GNOME/gjs/-/tree/master/examples)

View File

@@ -0,0 +1,178 @@
// extension.js - Point d'entrée principal de l'extension Pilot Control
// Compatible avec GNOME Shell 45+
import GObject from 'gi://GObject';
import St from 'gi://St';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
import {YamlConfig} from './yamlConfig.js';
import {PilotWindow} from './ui/pilotWindow.js';
import {ServiceManager} from './serviceManager.js';
/**
* Bouton dans le panel GNOME Shell
*/
const PilotIndicator = GObject.registerClass(
class PilotIndicator extends PanelMenu.Button {
_init(extension) {
super._init(0.0, 'Pilot Control');
this._extension = extension;
this._yamlConfig = new YamlConfig();
this._serviceManager = new ServiceManager();
this._window = null;
// Icône dans le panel
const icon = new St.Icon({
icon_name: 'computer-symbolic',
style_class: 'system-status-icon',
});
this.add_child(icon);
// Menu items
this._buildMenu();
// Charger la config au démarrage
this._loadConfig();
}
/**
* Construit le menu déroulant
*/
_buildMenu() {
// Status du service
this._statusItem = new PopupMenu.PopupMenuItem('Status: Unknown', {
reactive: false,
});
this.menu.addMenuItem(this._statusItem);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
// Bouton pour ouvrir la fenêtre principale
const openWindowItem = new PopupMenu.PopupMenuItem('Open Control Panel');
openWindowItem.connect('activate', () => {
this._openMainWindow();
});
this.menu.addMenuItem(openWindowItem);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
// Actions rapides
const startServiceItem = new PopupMenu.PopupMenuItem('Start Service');
startServiceItem.connect('activate', () => {
this._serviceManager.startService();
this._updateStatus();
});
this.menu.addMenuItem(startServiceItem);
const stopServiceItem = new PopupMenu.PopupMenuItem('Stop Service');
stopServiceItem.connect('activate', () => {
this._serviceManager.stopService();
this._updateStatus();
});
this.menu.addMenuItem(stopServiceItem);
const restartServiceItem = new PopupMenu.PopupMenuItem('Restart Service');
restartServiceItem.connect('activate', () => {
this._serviceManager.restartService();
this._updateStatus();
});
this.menu.addMenuItem(restartServiceItem);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
// Reload config
const reloadConfigItem = new PopupMenu.PopupMenuItem('Reload Config');
reloadConfigItem.connect('activate', () => {
this._loadConfig();
Main.notify('Pilot Control', 'Configuration reloaded');
});
this.menu.addMenuItem(reloadConfigItem);
}
/**
* Charge la configuration depuis le fichier YAML
*/
_loadConfig() {
const config = this._yamlConfig.load();
if (config) {
console.log('Pilot config loaded successfully');
this._updateStatus();
} else {
console.error('Failed to load Pilot config');
Main.notify('Pilot Control', 'Failed to load configuration');
}
}
/**
* Met à jour le status du service dans le menu
*/
_updateStatus() {
const isActive = this._serviceManager.isServiceActive();
const statusText = isActive ? 'Running' : 'Stopped';
const statusIcon = isActive ? '🟢' : '🔴';
this._statusItem.label.text = `Status: ${statusIcon} ${statusText}`;
}
/**
* Ouvre la fenêtre principale de contrôle
*/
_openMainWindow() {
if (this._window && !this._window.is_destroyed) {
this._window.present();
return;
}
this._window = new PilotWindow(
this._extension,
this._yamlConfig,
this._serviceManager
);
this._window.connect('destroy', () => {
this._window = null;
});
this._window.show();
}
/**
* Nettoyage lors de la destruction
*/
destroy() {
if (this._window && !this._window.is_destroyed) {
this._window.destroy();
}
super.destroy();
}
});
/**
* Extension principale
*/
export default class PilotExtension extends Extension {
enable() {
console.log('Enabling Pilot Control extension');
this._indicator = new PilotIndicator(this);
Main.panel.addToStatusArea(this.uuid, this._indicator);
}
disable() {
console.log('Disabling Pilot Control extension');
if (this._indicator) {
this._indicator.destroy();
this._indicator = null;
}
}
}

113
gnome-pilot-extension/install.sh Executable file
View File

@@ -0,0 +1,113 @@
#!/bin/bash
# install.sh - Script d'installation automatique de l'extension Pilot Control
set -e
EXTENSION_UUID="pilot-control@gnome-shell-extensions"
EXTENSION_DIR="$HOME/.local/share/gnome-shell/extensions/$EXTENSION_UUID"
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "================================================"
echo " Pilot Control - GNOME Shell Extension"
echo " Installation Script"
echo "================================================"
echo ""
# Vérifier que GNOME Shell est installé
if ! command -v gnome-shell &> /dev/null; then
echo "❌ Error: GNOME Shell is not installed"
exit 1
fi
# Vérifier la version de GNOME Shell
GNOME_VERSION=$(gnome-shell --version | grep -oP '\d+' | head -1)
if [ "$GNOME_VERSION" -lt 45 ]; then
echo "⚠️ Warning: This extension requires GNOME Shell 45 or higher"
echo " Current version: $GNOME_VERSION"
read -p " Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
echo "📋 Installation details:"
echo " Source: $SOURCE_DIR"
echo " Target: $EXTENSION_DIR"
echo ""
# Créer le répertoire de destination
echo "📁 Creating extension directory..."
mkdir -p "$EXTENSION_DIR"
# Copier les fichiers
echo "📦 Copying extension files..."
cp -v "$SOURCE_DIR/metadata.json" "$EXTENSION_DIR/"
cp -v "$SOURCE_DIR/extension.js" "$EXTENSION_DIR/"
cp -v "$SOURCE_DIR/prefs.js" "$EXTENSION_DIR/"
cp -v "$SOURCE_DIR/yamlConfig.js" "$EXTENSION_DIR/"
cp -v "$SOURCE_DIR/serviceManager.js" "$EXTENSION_DIR/"
cp -v "$SOURCE_DIR/stylesheet.css" "$EXTENSION_DIR/"
# Copier le répertoire UI
echo "📦 Copying UI files..."
mkdir -p "$EXTENSION_DIR/ui"
cp -v "$SOURCE_DIR/ui/"*.js "$EXTENSION_DIR/ui/"
# Vérifier l'installation
echo ""
if [ -f "$EXTENSION_DIR/metadata.json" ]; then
echo "✅ Extension installed successfully!"
echo ""
echo "📍 Installation location:"
echo " $EXTENSION_DIR"
echo ""
# Détecter le type de session
SESSION_TYPE="${XDG_SESSION_TYPE:-unknown}"
echo "🔄 Next steps:"
echo ""
echo "1. Restart GNOME Shell:"
if [ "$SESSION_TYPE" = "x11" ]; then
echo " • Press Alt+F2"
echo " • Type 'r' and press Enter"
else
echo " • Log out and log back in (Wayland session)"
fi
echo ""
echo "2. Enable the extension:"
echo " gnome-extensions enable $EXTENSION_UUID"
echo ""
echo " Or use the GNOME Extensions app"
echo ""
# Proposer d'activer l'extension immédiatement
read -p "🚀 Would you like to enable the extension now? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
if gnome-extensions enable "$EXTENSION_UUID" 2>/dev/null; then
echo "✅ Extension enabled!"
echo ""
if [ "$SESSION_TYPE" = "x11" ]; then
echo "⚠️ You still need to restart GNOME Shell (Alt+F2, 'r')"
else
echo "⚠️ You need to log out and log back in for changes to take effect"
fi
else
echo "⚠️ Could not enable extension automatically"
echo " Please enable it manually using GNOME Extensions app"
fi
fi
echo ""
echo "📖 For more information, see:"
echo " $SOURCE_DIR/README.md"
else
echo "❌ Installation failed - metadata.json not found"
exit 1
fi
echo ""
echo "================================================"

View File

@@ -0,0 +1,14 @@
{
"uuid": "pilot-control@gnome-shell-extensions",
"name": "Pilot Control",
"description": "Control Pilot V2 MQTT agent from GNOME Shell",
"version": 1,
"shell-version": [
"45",
"46",
"47",
"48"
],
"url": "https://github.com/yourusername/pilot",
"settings-schema": "org.gnome.shell.extensions.pilot-control"
}

View File

@@ -0,0 +1,72 @@
// prefs.js - Préférences de l'extension (optionnel pour V1)
import Adw from 'gi://Adw';
import Gtk from 'gi://Gtk';
import {ExtensionPreferences} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
export default class PilotPreferences extends ExtensionPreferences {
/**
* Remplit la fenêtre de préférences
*/
fillPreferencesWindow(window) {
// Page principale
const page = new Adw.PreferencesPage({
title: 'General',
icon_name: 'dialog-information-symbolic',
});
// Groupe: Configuration
const configGroup = new Adw.PreferencesGroup({
title: 'Configuration',
description: 'Extension settings for Pilot Control',
});
// Config file path
const configPathRow = new Adw.ActionRow({
title: 'Config File Path',
subtitle: 'Path to pilot-v2/config.yaml',
});
const configPathEntry = new Gtk.Entry({
text: '~/app/pilot/pilot-v2/config.yaml',
valign: Gtk.Align.CENTER,
hexpand: true,
});
configPathRow.add_suffix(configPathEntry);
configGroup.add(configPathRow);
// Service name
const serviceNameRow = new Adw.ActionRow({
title: 'Service Name',
subtitle: 'Systemd service name',
});
const serviceNameEntry = new Gtk.Entry({
text: 'mqtt_pilot.service',
valign: Gtk.Align.CENTER,
hexpand: true,
});
serviceNameRow.add_suffix(serviceNameEntry);
configGroup.add(serviceNameRow);
page.add(configGroup);
// Groupe: About
const aboutGroup = new Adw.PreferencesGroup({
title: 'About',
});
const aboutRow = new Adw.ActionRow({
title: 'Pilot Control Extension',
subtitle: 'Version 1.0\n\nControl Pilot V2 MQTT agent from GNOME Shell',
});
aboutGroup.add(aboutRow);
page.add(aboutGroup);
window.add(page);
}
}

View File

@@ -0,0 +1,153 @@
// serviceManager.js - Gestion du service systemd Pilot V2
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
/**
* Gère les interactions avec le service systemd mqtt_pilot
*/
export class ServiceManager {
constructor() {
this.serviceName = 'mqtt_pilot.service';
}
/**
* Exécute une commande systemctl
*/
_executeSystemctl(action) {
try {
const command = `systemctl --user ${action} ${this.serviceName}`;
const [success, stdout, stderr, exitStatus] = GLib.spawn_command_line_sync(command);
if (exitStatus !== 0) {
const decoder = new TextDecoder('utf-8');
const errorMsg = decoder.decode(stderr);
console.error(`systemctl ${action} failed: ${errorMsg}`);
return false;
}
return true;
} catch (error) {
console.error(`Error executing systemctl: ${error.message}`);
return false;
}
}
/**
* Vérifie si le service est actif
*/
isServiceActive() {
try {
const command = `systemctl --user is-active ${this.serviceName}`;
const [success, stdout, stderr, exitStatus] = GLib.spawn_command_line_sync(command);
// Exit status 0 = active, 3 = inactive
return exitStatus === 0;
} catch (error) {
console.error(`Error checking service status: ${error.message}`);
return false;
}
}
/**
* Vérifie si le service est enabled
*/
isServiceEnabled() {
try {
const command = `systemctl --user is-enabled ${this.serviceName}`;
const [success, stdout, stderr, exitStatus] = GLib.spawn_command_line_sync(command);
return exitStatus === 0;
} catch (error) {
console.error(`Error checking service enabled status: ${error.message}`);
return false;
}
}
/**
* Démarre le service
*/
startService() {
console.log('Starting Pilot service...');
return this._executeSystemctl('start');
}
/**
* Arrête le service
*/
stopService() {
console.log('Stopping Pilot service...');
return this._executeSystemctl('stop');
}
/**
* Redémarre le service
*/
restartService() {
console.log('Restarting Pilot service...');
return this._executeSystemctl('restart');
}
/**
* Active le service au démarrage
*/
enableService() {
console.log('Enabling Pilot service...');
return this._executeSystemctl('enable');
}
/**
* Désactive le service au démarrage
*/
disableService() {
console.log('Disabling Pilot service...');
return this._executeSystemctl('disable');
}
/**
* Recharge la configuration du service
*/
reloadService() {
console.log('Reloading Pilot service configuration...');
// Pour Pilot V2, on redémarre le service pour recharger la config
return this.restartService();
}
/**
* Obtient le status détaillé du service
*/
getServiceStatus() {
try {
const command = `systemctl --user status ${this.serviceName}`;
const [success, stdout, stderr, exitStatus] = GLib.spawn_command_line_sync(command);
const decoder = new TextDecoder('utf-8');
return {
active: exitStatus === 0,
output: decoder.decode(stdout)
};
} catch (error) {
console.error(`Error getting service status: ${error.message}`);
return {
active: false,
output: error.message
};
}
}
/**
* Obtient les derniers logs du service
*/
getServiceLogs(lines = 20) {
try {
const command = `journalctl --user -u ${this.serviceName} -n ${lines} --no-pager`;
const [success, stdout, stderr, exitStatus] = GLib.spawn_command_line_sync(command);
const decoder = new TextDecoder('utf-8');
return decoder.decode(stdout);
} catch (error) {
console.error(`Error getting service logs: ${error.message}`);
return `Error: ${error.message}`;
}
}
}

View File

@@ -0,0 +1,25 @@
/* stylesheet.css - Styles personnalisés pour l'extension Pilot Control */
/* Style général pour les warnings */
.warning {
color: #ff7800;
font-weight: bold;
}
/* Labels atténués */
.dim-label {
opacity: 0.6;
font-size: 0.9em;
}
/* Boutons d'action suggérés */
.suggested-action {
background-color: #3584e4;
color: white;
}
/* Liste boxed standard */
.boxed-list {
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.05);
}

229
gnome-pilot-extension/test.sh Executable file
View File

@@ -0,0 +1,229 @@
#!/bin/bash
# test.sh - Script de test rapide pour l'extension Pilot Control
set -e
echo "================================================"
echo " Pilot Control Extension - Tests"
echo "================================================"
echo ""
# Couleurs pour l'affichage
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Fonction pour afficher un test réussi
pass() {
echo -e "${GREEN}${NC} $1"
}
# Fonction pour afficher un test échoué
fail() {
echo -e "${RED}${NC} $1"
}
# Fonction pour afficher un warning
warn() {
echo -e "${YELLOW}${NC} $1"
}
# Test 1: Vérifier que GNOME Shell est installé
echo "Test 1: GNOME Shell installation"
if command -v gnome-shell &> /dev/null; then
VERSION=$(gnome-shell --version)
pass "GNOME Shell is installed: $VERSION"
else
fail "GNOME Shell is not installed"
exit 1
fi
echo ""
# Test 2: Vérifier la version de GNOME Shell
echo "Test 2: GNOME Shell version"
GNOME_VERSION=$(gnome-shell --version | grep -oP '\d+' | head -1)
if [ "$GNOME_VERSION" -ge 45 ]; then
pass "GNOME Shell version is compatible: $GNOME_VERSION"
else
warn "GNOME Shell version may not be compatible: $GNOME_VERSION (requires 45+)"
fi
echo ""
# Test 3: Vérifier que l'extension est installée
echo "Test 3: Extension installation"
EXTENSION_UUID="pilot-control@gnome-shell-extensions"
EXTENSION_DIR="$HOME/.local/share/gnome-shell/extensions/$EXTENSION_UUID"
if [ -d "$EXTENSION_DIR" ]; then
pass "Extension directory exists: $EXTENSION_DIR"
else
fail "Extension is not installed"
echo " Run: ./install.sh"
exit 1
fi
echo ""
# Test 4: Vérifier les fichiers essentiels
echo "Test 4: Essential files"
ESSENTIAL_FILES=("metadata.json" "extension.js" "yamlConfig.js" "serviceManager.js")
for file in "${ESSENTIAL_FILES[@]}"; do
if [ -f "$EXTENSION_DIR/$file" ]; then
pass "$file exists"
else
fail "$file is missing"
fi
done
echo ""
# Test 5: Vérifier que l'extension est activée
echo "Test 5: Extension activation"
if gnome-extensions list --enabled 2>/dev/null | grep -q "$EXTENSION_UUID"; then
pass "Extension is enabled"
else
warn "Extension is not enabled"
echo " Run: gnome-extensions enable $EXTENSION_UUID"
fi
echo ""
# Test 6: Vérifier que le fichier config.yaml existe
echo "Test 6: Pilot V2 configuration"
CONFIG_PATHS=(
"$HOME/app/pilot/pilot-v2/config.yaml"
"$HOME/.config/pilot/config.yaml"
"/etc/pilot/config.yaml"
)
CONFIG_FOUND=false
for config_path in "${CONFIG_PATHS[@]}"; do
if [ -f "$config_path" ]; then
pass "Config file found: $config_path"
CONFIG_FOUND=true
CONFIG_PATH="$config_path"
break
fi
done
if [ "$CONFIG_FOUND" = false ]; then
warn "Config file not found in standard locations"
echo " Checked:"
for config_path in "${CONFIG_PATHS[@]}"; do
echo " - $config_path"
done
fi
echo ""
# Test 7: Vérifier que le service systemd existe
echo "Test 7: Systemd service"
SERVICE_NAME="mqtt_pilot.service"
if systemctl --user list-unit-files 2>/dev/null | grep -q "$SERVICE_NAME"; then
pass "Service exists: $SERVICE_NAME"
# Vérifier le statut
if systemctl --user is-active --quiet "$SERVICE_NAME"; then
pass "Service is running"
else
warn "Service is not running"
echo " Run: systemctl --user start $SERVICE_NAME"
fi
# Vérifier si enabled
if systemctl --user is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then
pass "Service is enabled (auto-start)"
else
warn "Service is not enabled"
echo " Run: systemctl --user enable $SERVICE_NAME"
fi
else
warn "Service not found: $SERVICE_NAME"
echo " Make sure Pilot V2 is installed and configured"
fi
echo ""
# Test 8: Vérifier les permissions du config.yaml
if [ "$CONFIG_FOUND" = true ]; then
echo "Test 8: Configuration file permissions"
if [ -r "$CONFIG_PATH" ]; then
pass "Config file is readable"
else
fail "Config file is not readable"
fi
if [ -w "$CONFIG_PATH" ]; then
pass "Config file is writable"
else
warn "Config file is not writable (extension won't be able to save changes)"
fi
echo ""
fi
# Test 9: Vérifier les backups existants
if [ "$CONFIG_FOUND" = true ]; then
echo "Test 9: Configuration backups"
BACKUP_DIR=$(dirname "$CONFIG_PATH")
BACKUP_COUNT=$(ls -1 "$BACKUP_DIR"/config.yaml.backup_* 2>/dev/null | wc -l)
if [ "$BACKUP_COUNT" -gt 0 ]; then
pass "Found $BACKUP_COUNT backup(s)"
echo " Latest: $(ls -1t "$BACKUP_DIR"/config.yaml.backup_* 2>/dev/null | head -1)"
else
warn "No backups found (will be created when you save)"
fi
echo ""
fi
# Test 10: Vérifier les logs GNOME Shell (optionnel)
echo "Test 10: GNOME Shell logs (checking for errors)"
if journalctl -b -o cat /usr/bin/gnome-shell 2>/dev/null | grep -i "pilot.*error" | tail -5 | grep -q "pilot"; then
warn "Found potential errors in GNOME Shell logs"
echo " Check logs with: journalctl -f -o cat /usr/bin/gnome-shell | grep -i pilot"
else
pass "No recent errors in GNOME Shell logs"
fi
echo ""
# Résumé
echo "================================================"
echo " Test Summary"
echo "================================================"
echo ""
# Compter les tests réussis
PASSED_COUNT=0
TOTAL_TESTS=10
# Note: Cette approche simplifiée compte manuellement
# Dans un vrai script de test, on utiliserait des variables
echo "Tests completed!"
echo ""
echo "Next steps:"
echo "1. If extension is not enabled, run:"
echo " gnome-extensions enable $EXTENSION_UUID"
echo ""
echo "2. Restart GNOME Shell:"
echo " - On X11: Alt+F2, type 'r', press Enter"
echo " - On Wayland: Log out and log back in"
echo ""
echo "3. Look for the Pilot Control icon in the top panel"
echo ""
echo "4. Check logs for any errors:"
echo " journalctl -f -o cat /usr/bin/gnome-shell | grep -i pilot"
echo ""
# Test interactif optionnel
read -p "Would you like to view the last 20 lines of GNOME Shell logs? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo ""
echo "Last 20 lines of GNOME Shell logs (filtering for 'pilot'):"
echo "-----------------------------------------------------------"
journalctl -b -o cat /usr/bin/gnome-shell 2>/dev/null | grep -i pilot | tail -20 || echo "No logs found"
fi
echo ""
echo "================================================"

View File

@@ -0,0 +1,153 @@
// ui/commandEditDialog.js - Dialogue d'édition pour la allowlist des commandes
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import Adw from 'gi://Adw';
/**
* Dialogue pour éditer la liste des commandes autorisées
*/
export const CommandEditDialog = GObject.registerClass(
class CommandEditDialog extends Adw.Window {
_init(parent, currentAllowlist) {
super._init({
transient_for: parent,
modal: true,
title: 'Edit Allowed Commands',
default_width: 500,
default_height: 400,
});
this._currentAllowlist = [...currentAllowlist];
this._buildUI();
}
/**
* Construit l'interface du dialogue
*/
_buildUI() {
// Header bar
const headerBar = new Adw.HeaderBar();
const cancelButton = new Gtk.Button({
label: 'Cancel',
});
cancelButton.connect('clicked', () => {
this.emit('response', Gtk.ResponseType.CANCEL);
});
headerBar.pack_start(cancelButton);
const saveButton = new Gtk.Button({
label: 'Save',
});
saveButton.add_css_class('suggested-action');
saveButton.connect('clicked', () => {
this.emit('response', Gtk.ResponseType.OK);
});
headerBar.pack_end(saveButton);
// Toolbar view
const toolbarView = new Adw.ToolbarView();
toolbarView.add_top_bar(headerBar);
// Main content
const contentBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 12,
margin_top: 12,
margin_bottom: 12,
margin_start: 12,
margin_end: 12,
});
// Info label
const infoLabel = new Gtk.Label({
label: 'Select which commands are allowed to be executed via MQTT:',
wrap: true,
xalign: 0,
});
contentBox.append(infoLabel);
// Available commands
const availableCommands = ['shutdown', 'reboot', 'sleep', 'hibernate', 'screen'];
// List box for commands
const listBox = new Gtk.ListBox({
selection_mode: Gtk.SelectionMode.NONE,
});
listBox.add_css_class('boxed-list');
this._commandCheckboxes = {};
for (const command of availableCommands) {
const row = new Adw.ActionRow({
title: command.charAt(0).toUpperCase() + command.slice(1),
subtitle: this._getCommandDescription(command),
});
const checkbox = new Gtk.CheckButton({
active: this._currentAllowlist.includes(command),
valign: Gtk.Align.CENTER,
});
this._commandCheckboxes[command] = checkbox;
row.add_suffix(checkbox);
row.activatable_widget = checkbox;
listBox.append(row);
}
// Scrolled window
const scrolledWindow = new Gtk.ScrolledWindow({
vexpand: true,
hscrollbar_policy: Gtk.PolicyType.NEVER,
});
scrolledWindow.set_child(listBox);
contentBox.append(scrolledWindow);
// Warning label
const warningLabel = new Gtk.Label({
label: '⚠️ Warning: These commands have system-wide effects. Enable only commands you need.',
wrap: true,
xalign: 0,
});
warningLabel.add_css_class('warning');
contentBox.append(warningLabel);
toolbarView.set_content(contentBox);
this.set_content(toolbarView);
}
/**
* Retourne une description pour chaque commande
*/
_getCommandDescription(command) {
const descriptions = {
'shutdown': 'Power off the system',
'reboot': 'Restart the system',
'sleep': 'Suspend to RAM (sleep mode)',
'hibernate': 'Suspend to disk (hibernate)',
'screen': 'Control screen on/off state',
};
return descriptions[command] || 'No description available';
}
/**
* Récupère la nouvelle allowlist
*/
getAllowlist() {
const allowlist = [];
for (const [command, checkbox] of Object.entries(this._commandCheckboxes)) {
if (checkbox.active) {
allowlist.push(command);
}
}
return allowlist;
}
});

View File

@@ -0,0 +1,155 @@
// ui/metricEditDialog.js - Dialogue d'édition pour une métrique de télémétrie
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import Adw from 'gi://Adw';
/**
* Dialogue pour éditer les propriétés d'une métrique
*/
export const MetricEditDialog = GObject.registerClass(
class MetricEditDialog extends Adw.MessageDialog {
_init(parent, metricKey, currentConfig) {
super._init({
transient_for: parent,
modal: true,
heading: `Edit Metric: ${metricKey}`,
});
this._metricKey = metricKey;
this._currentConfig = currentConfig;
this.add_response('cancel', 'Cancel');
this.add_response('ok', 'Save');
this.set_default_response('ok');
this.set_close_response('cancel');
this._buildForm();
}
/**
* Construit le formulaire d'édition
*/
_buildForm() {
const contentBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 12,
margin_top: 12,
margin_bottom: 12,
margin_start: 12,
margin_end: 12,
});
// Enabled switch
const enabledBox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 12,
});
const enabledLabel = new Gtk.Label({
label: 'Enabled:',
xalign: 0,
hexpand: true,
});
this._enabledSwitch = new Gtk.Switch({
active: this._currentConfig.enabled || false,
valign: Gtk.Align.CENTER,
});
enabledBox.append(enabledLabel);
enabledBox.append(this._enabledSwitch);
contentBox.append(enabledBox);
// Name entry
const nameBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 6,
});
const nameLabel = new Gtk.Label({
label: 'Display Name:',
xalign: 0,
});
this._nameEntry = new Gtk.Entry({
text: this._currentConfig.name || '',
placeholder_text: 'Enter metric display name',
});
nameBox.append(nameLabel);
nameBox.append(this._nameEntry);
contentBox.append(nameBox);
// Unique ID entry
const uniqueIdBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 6,
});
const uniqueIdLabel = new Gtk.Label({
label: 'Unique ID:',
xalign: 0,
});
this._uniqueIdEntry = new Gtk.Entry({
text: this._currentConfig.unique_id || '',
placeholder_text: 'Enter unique identifier',
});
uniqueIdBox.append(uniqueIdLabel);
uniqueIdBox.append(this._uniqueIdEntry);
contentBox.append(uniqueIdBox);
// Interval spin button
const intervalBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 6,
});
const intervalLabel = new Gtk.Label({
label: 'Update Interval (seconds):',
xalign: 0,
});
this._intervalSpin = new Gtk.SpinButton({
adjustment: new Gtk.Adjustment({
lower: 1,
upper: 3600,
step_increment: 1,
page_increment: 10,
value: this._currentConfig.interval_s || 30,
}),
climb_rate: 1,
digits: 0,
});
intervalBox.append(intervalLabel);
intervalBox.append(this._intervalSpin);
contentBox.append(intervalBox);
// Info label
const infoLabel = new Gtk.Label({
label: 'Note: Changes require service restart to take effect',
wrap: true,
xalign: 0,
});
infoLabel.add_css_class('dim-label');
contentBox.append(infoLabel);
this.set_extra_child(contentBox);
}
/**
* Récupère les modifications effectuées
*/
getUpdates() {
return {
enabled: this._enabledSwitch.active,
name: this._nameEntry.text.trim() || this._currentConfig.name,
unique_id: this._uniqueIdEntry.text.trim() || this._currentConfig.unique_id,
interval_s: this._intervalSpin.value,
};
}
});

View File

@@ -0,0 +1,461 @@
// ui/pilotWindow.js - Fenêtre principale de l'extension Pilot Control
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import {MetricEditDialog} from './metricEditDialog.js';
import {CommandEditDialog} from './commandEditDialog.js';
/**
* Fenêtre principale avec les sections Services, Telemetry, Commands
*/
export const PilotWindow = GObject.registerClass(
class PilotWindow extends Adw.Window {
_init(extension, yamlConfig, serviceManager) {
super._init({
title: 'Pilot Control Panel',
default_width: 800,
default_height: 600,
});
this._extension = extension;
this._yamlConfig = yamlConfig;
this._serviceManager = serviceManager;
this._buildUI();
this._loadData();
}
/**
* Construit l'interface utilisateur
*/
_buildUI() {
// Header bar
const headerBar = new Adw.HeaderBar();
// Bouton refresh
const refreshButton = new Gtk.Button({
icon_name: 'view-refresh-symbolic',
tooltip_text: 'Reload configuration',
});
refreshButton.connect('clicked', () => {
this._loadData();
});
headerBar.pack_end(refreshButton);
// Bouton save
const saveButton = new Gtk.Button({
icon_name: 'document-save-symbolic',
tooltip_text: 'Save configuration',
});
saveButton.connect('clicked', () => {
this._saveConfig();
});
headerBar.pack_end(saveButton);
// Toolbar view (GNOME 45+)
const toolbarView = new Adw.ToolbarView();
toolbarView.add_top_bar(headerBar);
// Main content box
const mainBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
margin_top: 12,
margin_bottom: 12,
margin_start: 12,
margin_end: 12,
spacing: 12,
});
// Scrolled window
const scrolledWindow = new Gtk.ScrolledWindow({
vexpand: true,
hscrollbar_policy: Gtk.PolicyType.NEVER,
});
scrolledWindow.set_child(mainBox);
toolbarView.set_content(scrolledWindow);
this.set_content(toolbarView);
// Section: Service Control
mainBox.append(this._buildServiceSection());
// Section: Telemetry Metrics
mainBox.append(this._buildTelemetrySection());
// Section: Commands
mainBox.append(this._buildCommandsSection());
}
/**
* Construit la section Service Control
*/
_buildServiceSection() {
const group = new Adw.PreferencesGroup({
title: 'Service Control',
description: 'Manage the Pilot systemd service',
});
// Service status row
this._serviceStatusRow = new Adw.ActionRow({
title: 'Service Status',
subtitle: 'Unknown',
});
const serviceSwitch = new Gtk.Switch({
valign: Gtk.Align.CENTER,
});
serviceSwitch.connect('notify::active', (sw) => {
if (sw.active) {
this._serviceManager.startService();
} else {
this._serviceManager.stopService();
}
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
this._updateServiceStatus();
return GLib.SOURCE_REMOVE;
});
});
this._serviceSwitch = serviceSwitch;
this._serviceStatusRow.add_suffix(serviceSwitch);
this._serviceStatusRow.activatable_widget = serviceSwitch;
group.add(this._serviceStatusRow);
// Service auto-start row
this._serviceEnableRow = new Adw.ActionRow({
title: 'Auto-start Service',
subtitle: 'Enable service at system startup',
});
const enableSwitch = new Gtk.Switch({
valign: Gtk.Align.CENTER,
});
enableSwitch.connect('notify::active', (sw) => {
if (sw.active) {
this._serviceManager.enableService();
} else {
this._serviceManager.disableService();
}
});
this._serviceEnableSwitch = enableSwitch;
this._serviceEnableRow.add_suffix(enableSwitch);
this._serviceEnableRow.activatable_widget = enableSwitch;
group.add(this._serviceEnableRow);
// Restart button row
const restartRow = new Adw.ActionRow({
title: 'Restart Service',
subtitle: 'Apply configuration changes',
});
const restartButton = new Gtk.Button({
label: 'Restart',
valign: Gtk.Align.CENTER,
});
restartButton.connect('clicked', () => {
this._serviceManager.restartService();
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
this._updateServiceStatus();
return GLib.SOURCE_REMOVE;
});
});
restartRow.add_suffix(restartButton);
group.add(restartRow);
return group;
}
/**
* Construit la section Telemetry
*/
_buildTelemetrySection() {
const group = new Adw.PreferencesGroup({
title: 'Telemetry Metrics',
description: 'Configure system monitoring metrics',
});
// Global telemetry switch
this._telemetryGlobalRow = new Adw.ActionRow({
title: 'Enable Telemetry',
subtitle: 'Master switch for all metrics',
});
const telemetrySwitch = new Gtk.Switch({
valign: Gtk.Align.CENTER,
});
telemetrySwitch.connect('notify::active', (sw) => {
this._yamlConfig.setTelemetryEnabled(sw.active);
this._markDirty();
});
this._telemetrySwitch = telemetrySwitch;
this._telemetryGlobalRow.add_suffix(telemetrySwitch);
this._telemetryGlobalRow.activatable_widget = telemetrySwitch;
group.add(this._telemetryGlobalRow);
// Container pour les métriques individuelles
this._telemetryMetricsBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 0,
});
group.add(this._telemetryMetricsBox);
return group;
}
/**
* Construit la section Commands
*/
_buildCommandsSection() {
const group = new Adw.PreferencesGroup({
title: 'Commands',
description: 'Configure allowed system commands',
});
// Global commands switch
this._commandsGlobalRow = new Adw.ActionRow({
title: 'Enable Commands',
subtitle: 'Master switch for all commands',
});
const commandsSwitch = new Gtk.Switch({
valign: Gtk.Align.CENTER,
});
commandsSwitch.connect('notify::active', (sw) => {
this._yamlConfig.setCommandsEnabled(sw.active);
this._markDirty();
});
this._commandsSwitch = commandsSwitch;
this._commandsGlobalRow.add_suffix(commandsSwitch);
this._commandsGlobalRow.activatable_widget = commandsSwitch;
group.add(this._commandsGlobalRow);
// Allowlist editor row
this._commandsAllowlistRow = new Adw.ActionRow({
title: 'Allowed Commands',
subtitle: 'Click to edit the allowlist',
});
const editButton = new Gtk.Button({
icon_name: 'document-edit-symbolic',
valign: Gtk.Align.CENTER,
});
editButton.connect('clicked', () => {
this._editCommandsAllowlist();
});
this._commandsAllowlistRow.add_suffix(editButton);
this._commandsAllowlistRow.set_activatable(true);
this._commandsAllowlistRow.connect('activated', () => {
this._editCommandsAllowlist();
});
group.add(this._commandsAllowlistRow);
return group;
}
/**
* Charge les données depuis la config YAML
*/
_loadData() {
const config = this._yamlConfig.load();
if (!config) {
this._showError('Failed to load configuration');
return;
}
// Update service status
this._updateServiceStatus();
// Update telemetry section
const telemetryEnabled = config.features?.telemetry?.enabled || false;
this._telemetrySwitch.active = telemetryEnabled;
// Clear existing metrics
let child = this._telemetryMetricsBox.get_first_child();
while (child) {
const next = child.get_next_sibling();
this._telemetryMetricsBox.remove(child);
child = next;
}
// Add metrics
const metrics = this._yamlConfig.getTelemetryMetrics();
for (const [name, metricConfig] of Object.entries(metrics)) {
this._addMetricRow(name, metricConfig);
}
// Update commands section
const commandsEnabled = config.features?.commands?.enabled || false;
this._commandsSwitch.active = commandsEnabled;
const allowlist = this._yamlConfig.getCommandsAllowlist();
this._commandsAllowlistRow.subtitle = `${allowlist.length} commands allowed`;
this._dirtyConfig = false;
}
/**
* Ajoute une ligne pour une métrique
*/
_addMetricRow(name, metricConfig) {
const row = new Adw.ActionRow({
title: metricConfig.name || name,
subtitle: `Interval: ${metricConfig.interval_s || 'N/A'}s`,
});
// Switch pour enable/disable
const metricSwitch = new Gtk.Switch({
active: metricConfig.enabled || false,
valign: Gtk.Align.CENTER,
});
metricSwitch.connect('notify::active', (sw) => {
this._yamlConfig.updateTelemetryMetric(name, {enabled: sw.active});
this._markDirty();
});
// Bouton edit
const editButton = new Gtk.Button({
icon_name: 'document-edit-symbolic',
valign: Gtk.Align.CENTER,
});
editButton.connect('clicked', () => {
this._editMetric(name, metricConfig);
});
row.add_suffix(metricSwitch);
row.add_suffix(editButton);
this._telemetryMetricsBox.append(row);
}
/**
* Édite une métrique
*/
_editMetric(name, currentConfig) {
const dialog = new MetricEditDialog(this, name, currentConfig);
dialog.connect('response', (dlg, responseId) => {
if (responseId === Gtk.ResponseType.OK) {
const updates = dialog.getUpdates();
this._yamlConfig.updateTelemetryMetric(name, updates);
this._markDirty();
this._loadData();
}
dialog.destroy();
});
dialog.present();
}
/**
* Édite la allowlist des commandes
*/
_editCommandsAllowlist() {
const currentAllowlist = this._yamlConfig.getCommandsAllowlist();
const dialog = new CommandEditDialog(this, currentAllowlist);
dialog.connect('response', (dlg, responseId) => {
if (responseId === Gtk.ResponseType.OK) {
const newAllowlist = dialog.getAllowlist();
this._yamlConfig.updateCommandsAllowlist(newAllowlist);
this._markDirty();
this._loadData();
}
dialog.destroy();
});
dialog.present();
}
/**
* Met à jour le status du service
*/
_updateServiceStatus() {
const isActive = this._serviceManager.isServiceActive();
const isEnabled = this._serviceManager.isServiceEnabled();
this._serviceSwitch.active = isActive;
this._serviceEnableSwitch.active = isEnabled;
const statusText = isActive ? '🟢 Running' : '🔴 Stopped';
this._serviceStatusRow.subtitle = statusText;
}
/**
* Marque la config comme modifiée
*/
_markDirty() {
this._dirtyConfig = true;
}
/**
* Sauvegarde la configuration
*/
_saveConfig() {
if (!this._dirtyConfig) {
this._showInfo('No changes to save');
return;
}
const success = this._yamlConfig.save();
if (success) {
this._dirtyConfig = false;
this._showInfo('Configuration saved successfully');
// Recharger le service
this._serviceManager.reloadService();
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
this._updateServiceStatus();
return GLib.SOURCE_REMOVE;
});
} else {
this._showError('Failed to save configuration');
}
}
/**
* Affiche un message d'information
*/
_showInfo(message) {
const toast = new Adw.Toast({
title: message,
timeout: 2,
});
// Note: Toast overlay nécessite Adw.ToastOverlay
// Pour l'instant, on utilise console.log
console.log(`Info: ${message}`);
}
/**
* Affiche un message d'erreur
*/
_showError(message) {
const dialog = new Adw.MessageDialog({
transient_for: this,
heading: 'Error',
body: message,
});
dialog.add_response('ok', 'OK');
dialog.set_default_response('ok');
dialog.set_close_response('ok');
dialog.present();
}
});

View File

@@ -0,0 +1,280 @@
// yamlConfig.js - Module pour lire/écrire le fichier config.yaml de Pilot V2
// Version simple sans dépendances externes
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
/**
* Parser YAML simple - ne gère que les structures basiques nécessaires pour config.yaml
* Format attendu: clés avec indentation de 2 espaces
*/
export class YamlConfig {
constructor(configPath = null) {
// Chemins par défaut où chercher le config.yaml
this.configPath = configPath || this._findConfigPath();
this.config = null;
}
/**
* Trouve le fichier de configuration dans les emplacements standards
*/
_findConfigPath() {
const possiblePaths = [
GLib.build_filenamev([GLib.get_home_dir(), 'app/pilot/pilot-v2/config.yaml']),
GLib.build_filenamev([GLib.get_home_dir(), '.config/pilot/config.yaml']),
'/etc/pilot/config.yaml',
'./pilot-v2/config.yaml'
];
for (const path of possiblePaths) {
if (GLib.file_test(path, GLib.FileTest.EXISTS)) {
return path;
}
}
// Par défaut
return GLib.build_filenamev([GLib.get_home_dir(), 'app/pilot/pilot-v2/config.yaml']);
}
/**
* Lit le fichier YAML et retourne un objet JavaScript
*/
load() {
try {
const file = Gio.File.new_for_path(this.configPath);
const [success, contents] = file.load_contents(null);
if (!success) {
throw new Error(`Cannot read file: ${this.configPath}`);
}
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(contents);
this.config = this._parseYaml(text);
return this.config;
} catch (error) {
console.error(`Error loading config: ${error.message}`);
return null;
}
}
/**
* Parse simple de YAML (gère uniquement la structure de config.yaml)
* Convertit YAML en objet JavaScript
*/
_parseYaml(text) {
const lines = text.split('\n');
const result = {};
const stack = [{obj: result, indent: -1}];
for (let line of lines) {
// Ignorer les commentaires et lignes vides
if (line.trim().startsWith('#') || line.trim() === '') {
continue;
}
const indent = line.search(/\S/);
const trimmed = line.trim();
// Calculer le niveau d'indentation (2 espaces = 1 niveau)
const level = Math.floor(indent / 2);
// Remonter dans la pile si nécessaire
while (stack.length > 0 && stack[stack.length - 1].indent >= level) {
stack.pop();
}
const parent = stack[stack.length - 1].obj;
// Traiter la ligne
if (trimmed.includes(':')) {
const colonIndex = trimmed.indexOf(':');
const key = trimmed.substring(0, colonIndex).trim();
let value = trimmed.substring(colonIndex + 1).trim();
if (value === '') {
// C'est un objet (nouvelle section)
parent[key] = {};
stack.push({obj: parent[key], indent: level});
} else if (value === 'true' || value === 'false') {
// Boolean
parent[key] = value === 'true';
} else if (!isNaN(value) && value !== '') {
// Number
parent[key] = Number(value);
} else {
// String (enlever les quotes si présentes)
parent[key] = value.replace(/^["']|["']$/g, '');
}
} else if (trimmed.startsWith('- ')) {
// Liste
if (!Array.isArray(parent)) {
// Convertir le parent en tableau si ce n'est pas déjà le cas
const lastKey = Object.keys(stack[stack.length - 2].obj).pop();
stack[stack.length - 2].obj[lastKey] = [];
stack[stack.length - 1].obj = stack[stack.length - 2].obj[lastKey];
}
const value = trimmed.substring(2).trim();
parent.push(value.replace(/^["']|["']$/g, ''));
}
}
return result;
}
/**
* Sauvegarde l'objet JavaScript en YAML
*/
save(config = null) {
const dataToSave = config || this.config;
if (!dataToSave) {
throw new Error('No config data to save');
}
try {
const yamlText = this._toYaml(dataToSave);
const file = Gio.File.new_for_path(this.configPath);
// Créer une sauvegarde
this._createBackup();
// Écrire le nouveau fichier
file.replace_contents(
new TextEncoder().encode(yamlText),
null,
false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
null
);
console.log(`Config saved to: ${this.configPath}`);
return true;
} catch (error) {
console.error(`Error saving config: ${error.message}`);
return false;
}
}
/**
* Convertit un objet JavaScript en YAML
*/
_toYaml(obj, indent = 0) {
let yaml = '';
const spaces = ' '.repeat(indent);
for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined) {
continue;
}
if (typeof value === 'object' && !Array.isArray(value)) {
// Objet imbriqué
yaml += `${spaces}${key}:\n`;
yaml += this._toYaml(value, indent + 1);
} else if (Array.isArray(value)) {
// Tableau
yaml += `${spaces}${key}:\n`;
for (const item of value) {
yaml += `${spaces} - ${item}\n`;
}
} else {
// Valeur simple
const valueStr = typeof value === 'string' ? value : String(value);
yaml += `${spaces}${key}: ${valueStr}\n`;
}
}
return yaml;
}
/**
* Crée une sauvegarde du fichier de config actuel
*/
_createBackup() {
try {
const timestamp = GLib.DateTime.new_now_local().format('%Y%m%d_%H%M%S');
const backupPath = `${this.configPath}.backup_${timestamp}`;
const source = Gio.File.new_for_path(this.configPath);
const dest = Gio.File.new_for_path(backupPath);
if (source.query_exists(null)) {
source.copy(dest, Gio.FileCopyFlags.OVERWRITE, null, null);
console.log(`Backup created: ${backupPath}`);
}
} catch (error) {
console.warn(`Could not create backup: ${error.message}`);
}
}
/**
* Obtient les métriques de télémétrie configurées
*/
getTelemetryMetrics() {
if (!this.config?.features?.telemetry?.metrics) {
return {};
}
return this.config.features.telemetry.metrics;
}
/**
* Obtient la liste des commandes autorisées
*/
getCommandsAllowlist() {
if (!this.config?.features?.commands?.allowlist) {
return [];
}
return this.config.features.commands.allowlist;
}
/**
* Met à jour une métrique de télémétrie
*/
updateTelemetryMetric(metricName, updates) {
if (!this.config?.features?.telemetry?.metrics?.[metricName]) {
return false;
}
Object.assign(this.config.features.telemetry.metrics[metricName], updates);
return true;
}
/**
* Active/désactive la télémétrie globale
*/
setTelemetryEnabled(enabled) {
if (!this.config?.features?.telemetry) {
return false;
}
this.config.features.telemetry.enabled = enabled;
return true;
}
/**
* Active/désactive les commandes globales
*/
setCommandsEnabled(enabled) {
if (!this.config?.features?.commands) {
return false;
}
this.config.features.commands.enabled = enabled;
return true;
}
/**
* Met à jour la liste des commandes autorisées
*/
updateCommandsAllowlist(newAllowlist) {
if (!this.config?.features?.commands) {
return false;
}
this.config.features.commands.allowlist = newAllowlist;
return true;
}
}

1
pilot-v2/Cargo.lock generated
View File

@@ -952,6 +952,7 @@ dependencies = [
"tokio",
"tracing",
"tracing-subscriber",
"windows-sys 0.52.0",
"zbus",
]

View File

@@ -18,3 +18,6 @@ sysinfo = "0.30"
local-ip-address = "0.6"
zbus = "3"
hostname = "0.4"
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.52", features = ["Win32_System_Power"] }

View File

@@ -18,15 +18,280 @@ mqtt:
keepalive_s: 60
qos: 0
retain_states: true
reconnect:
attempts: 3
retry_delay_s: 1
short_wait_s: 60
long_wait_s: 3600
features:
telemetry:
enabled: true
interval_s: 10
metrics:
cpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "CPU Usage"
unique_id: "$hostname_cpu_usage"
unit: "%"
device_class: ""
icon: "mdi:chip"
state_class: "measurement"
pilot_v2_cpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "Pilot V2 CPU Usage"
unique_id: "$hostname_pilot_v2_cpu_usage"
unit: "%"
device_class: ""
icon: "mdi:application"
state_class: "measurement"
pilot_v2_mem_used_mb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "Pilot V2 Memory Used"
unique_id: "$hostname_pilot_v2_mem_used"
unit: "MB"
device_class: ""
icon: "mdi:application"
state_class: "measurement"
cpu_temp_c:
enabled: true
discovery_enabled: true
interval_s: 30
name: "CPU Temp"
unique_id: "$hostname_cpu_temp"
unit: "°C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
ssd_temp_c:
enabled: true
discovery_enabled: true
interval_s: 60
name: "SSD Temp"
unique_id: "$hostname_ssd_temp"
unit: "°C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
gpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU Usage"
unique_id: "$hostname_gpu_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
gpu0_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU0 Usage"
unique_id: "$hostname_gpu0_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
gpu1_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU1 Usage"
unique_id: "$hostname_gpu1_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
gpu0_temp_c:
enabled: true
discovery_enabled: true
interval_s: 30
name: "GPU0 Temp"
unique_id: "$hostname_gpu0_temp"
unit: "°C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
gpu1_temp_c:
enabled: true
discovery_enabled: true
interval_s: 30
name: "GPU1 Temp"
unique_id: "$hostname_gpu1_temp"
unit: "°C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
gpu0_mem_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU0 Memory Used"
unique_id: "$hostname_gpu0_mem_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
gpu1_mem_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "GPU1 Memory Used"
unique_id: "$hostname_gpu1_mem_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
amd_gpu_usage:
enabled: true
discovery_enabled: true
interval_s: 10
name: "AMD GPU Usage"
unique_id: "$hostname_amd_gpu_usage"
unit: "%"
device_class: ""
icon: "mdi:gpu"
state_class: "measurement"
amd_gpu_temp_c:
enabled: true
discovery_enabled: true
interval_s: 30
name: "AMD GPU Temp"
unique_id: "$hostname_amd_gpu_temp"
unit: "°C"
device_class: "temperature"
icon: "mdi:thermometer"
state_class: "measurement"
amd_gpu_mem_used_gb:
enabled: true
discovery_enabled: true
interval_s: 10
name: "AMD GPU Memory Used"
unique_id: "$hostname_amd_gpu_mem_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
memory_used_gb:
enabled: true
discovery_enabled: true
interval_s: 20
name: "Memory Used"
unique_id: "$hostname_memory_used"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: "measurement"
memory_total_gb:
enabled: true
discovery_enabled: true
interval_s: 240
name: "Memory Total"
unique_id: "$hostname_memory_total"
unit: "GB"
device_class: ""
icon: "mdi:memory"
state_class: ""
disk_free_gb:
enabled: true
discovery_enabled: true
interval_s: 240
name: "Disk Free"
unique_id: "$hostname_disk_free"
unit: "GB"
device_class: ""
icon: "mdi:harddisk"
state_class: "measurement"
fan_cpu_rpm:
enabled: true
discovery_enabled: true
interval_s: 23
name: "CPU Fan"
unique_id: "$hostname_fan_cpu"
unit: "RPM"
device_class: ""
icon: "mdi:fan"
state_class: "measurement"
fan_gpu_rpm:
enabled: true
discovery_enabled: true
interval_s: 23
name: "GPU Fan"
unique_id: "$hostname_fan_gpu"
unit: "RPM"
device_class: ""
icon: "mdi:fan"
state_class: "measurement"
ip_address:
enabled: true
discovery_enabled: true
interval_s: 1200
name: "IP Address"
unique_id: "$hostname_ip"
unit: ""
device_class: ""
icon: "mdi:ip"
state_class: ""
battery_level:
enabled: true
discovery_enabled: true
interval_s: 240
name: "Battery Level"
unique_id: "$hostname_battery_level"
unit: "%"
device_class: "battery"
icon: "mdi:battery"
state_class: "measurement"
battery_state:
enabled: true
discovery_enabled: true
interval_s: 240
name: "Battery State"
unique_id: "$hostname_battery_state"
unit: ""
device_class: ""
icon: "mdi:battery-charging"
state_class: ""
power_state:
enabled: true
discovery_enabled: true
interval_s: 240
name: "Power State"
unique_id: "$hostname_power_state"
unit: ""
device_class: ""
icon: "mdi:power"
state_class: ""
kernel:
enabled: true
discovery_enabled: true
interval_s: 14400
name: "Kernel"
unique_id: "$hostname_kernel"
unit: ""
device_class: ""
icon: "mdi:linux"
state_class: ""
os_version:
enabled: true
discovery_enabled: true
interval_s: 14400
name: "OS Version"
unique_id: "$hostname_os_version"
unit: ""
device_class: ""
icon: "mdi:desktop-classic"
state_class: ""
commands:
enabled: true
cooldown_s: 5
dry_run: true
dry_run: false # true = simule les commandes sans les executer
allowlist: ["shutdown", "reboot", "sleep", "screen"]
power_backend:
@@ -34,7 +299,7 @@ power_backend:
windows: "windows_service"
screen_backend:
linux: "gnome_busctl" # or x11_xset
linux: "x11_xset" #"gnome_busctl" # or "x11_xset"
windows: "winapi_session" # or external_tool
publish:

View File

@@ -2,6 +2,7 @@
// Il expose des structures de donnees simples pour le reste du code.
use anyhow::{bail, Context, Result};
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
@@ -41,6 +42,405 @@ fn default_sw_version() -> String {
"2.0.0".to_string()
}
fn default_telemetry_metrics() -> HashMap<String, TelemetryMetric> {
let mut metrics = HashMap::new();
metrics.insert(
"cpu_usage".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("CPU Usage".to_string()),
unique_id: None,
unit: Some("%".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:chip".to_string()),
},
);
metrics.insert(
"pilot_v2_cpu_usage".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("Pilot V2 CPU Usage".to_string()),
unique_id: None,
unit: Some("%".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:application".to_string()),
},
);
metrics.insert(
"pilot_v2_mem_used_mb".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("Pilot V2 Memory Used".to_string()),
unique_id: None,
unit: Some("MB".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:application".to_string()),
},
);
metrics.insert(
"cpu_temp_c".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("CPU Temp".to_string()),
unique_id: None,
unit: Some("°C".to_string()),
device_class: Some("temperature".to_string()),
state_class: Some("measurement".to_string()),
icon: Some("mdi:thermometer".to_string()),
},
);
metrics.insert(
"ssd_temp_c".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 30,
name: Some("SSD Temp".to_string()),
unique_id: None,
unit: Some("°C".to_string()),
device_class: Some("temperature".to_string()),
state_class: Some("measurement".to_string()),
icon: Some("mdi:thermometer".to_string()),
},
);
metrics.insert(
"gpu_usage".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("GPU Usage".to_string()),
unique_id: None,
unit: Some("%".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:gpu".to_string()),
},
);
metrics.insert(
"gpu0_usage".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("GPU0 Usage".to_string()),
unique_id: None,
unit: Some("%".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:gpu".to_string()),
},
);
metrics.insert(
"gpu1_usage".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("GPU1 Usage".to_string()),
unique_id: None,
unit: Some("%".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:gpu".to_string()),
},
);
metrics.insert(
"gpu0_temp_c".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("GPU0 Temp".to_string()),
unique_id: None,
unit: Some("°C".to_string()),
device_class: Some("temperature".to_string()),
state_class: Some("measurement".to_string()),
icon: Some("mdi:thermometer".to_string()),
},
);
metrics.insert(
"gpu1_temp_c".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("GPU1 Temp".to_string()),
unique_id: None,
unit: Some("°C".to_string()),
device_class: Some("temperature".to_string()),
state_class: Some("measurement".to_string()),
icon: Some("mdi:thermometer".to_string()),
},
);
metrics.insert(
"gpu0_mem_used_gb".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("GPU0 Memory Used".to_string()),
unique_id: None,
unit: Some("GB".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:memory".to_string()),
},
);
metrics.insert(
"gpu1_mem_used_gb".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("GPU1 Memory Used".to_string()),
unique_id: None,
unit: Some("GB".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:memory".to_string()),
},
);
metrics.insert(
"amd_gpu_usage".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("AMD GPU Usage".to_string()),
unique_id: None,
unit: Some("%".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:gpu".to_string()),
},
);
metrics.insert(
"amd_gpu_temp_c".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("AMD GPU Temp".to_string()),
unique_id: None,
unit: Some("°C".to_string()),
device_class: Some("temperature".to_string()),
state_class: Some("measurement".to_string()),
icon: Some("mdi:thermometer".to_string()),
},
);
metrics.insert(
"amd_gpu_mem_used_gb".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("AMD GPU Memory Used".to_string()),
unique_id: None,
unit: Some("GB".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:memory".to_string()),
},
);
metrics.insert(
"memory_used_gb".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("Memory Used".to_string()),
unique_id: None,
unit: Some("GB".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:memory".to_string()),
},
);
metrics.insert(
"memory_total_gb".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 60,
name: Some("Memory Total".to_string()),
unique_id: None,
unit: Some("GB".to_string()),
device_class: None,
state_class: None,
icon: Some("mdi:memory".to_string()),
},
);
metrics.insert(
"disk_free_gb".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 60,
name: Some("Disk Free".to_string()),
unique_id: None,
unit: Some("GB".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:harddisk".to_string()),
},
);
metrics.insert(
"fan_cpu_rpm".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("CPU Fan".to_string()),
unique_id: None,
unit: Some("RPM".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:fan".to_string()),
},
);
metrics.insert(
"fan_gpu_rpm".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 5,
name: Some("GPU Fan".to_string()),
unique_id: None,
unit: Some("RPM".to_string()),
device_class: None,
state_class: Some("measurement".to_string()),
icon: Some("mdi:fan".to_string()),
},
);
metrics.insert(
"ip_address".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 60,
name: Some("IP Address".to_string()),
unique_id: None,
unit: None,
device_class: None,
state_class: None,
icon: Some("mdi:ip".to_string()),
},
);
metrics.insert(
"battery_level".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 30,
name: Some("Battery Level".to_string()),
unique_id: None,
unit: Some("%".to_string()),
device_class: Some("battery".to_string()),
state_class: Some("measurement".to_string()),
icon: Some("mdi:battery".to_string()),
},
);
metrics.insert(
"battery_state".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 30,
name: Some("Battery State".to_string()),
unique_id: None,
unit: None,
device_class: None,
state_class: None,
icon: Some("mdi:battery-charging".to_string()),
},
);
metrics.insert(
"power_state".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 30,
name: Some("Power State".to_string()),
unique_id: None,
unit: None,
device_class: None,
state_class: None,
icon: Some("mdi:power".to_string()),
},
);
metrics.insert(
"kernel".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 3600,
name: Some("Kernel".to_string()),
unique_id: None,
unit: None,
device_class: None,
state_class: None,
icon: Some("mdi:linux".to_string()),
},
);
metrics.insert(
"os_version".to_string(),
TelemetryMetric {
enabled: true,
discovery_enabled: true,
interval_s: 3600,
name: Some("OS Version".to_string()),
unique_id: None,
unit: None,
device_class: None,
state_class: None,
icon: Some("mdi:desktop-classic".to_string()),
},
);
metrics
}
fn default_metric_enabled() -> bool {
true
}
fn default_metric_discovery_enabled() -> bool {
true
}
fn default_metric_interval_s() -> u64 {
10
}
fn default_mqtt_reconnect_attempts() -> u32 {
3
}
fn default_mqtt_reconnect_retry_delay_s() -> u64 {
1
}
fn default_mqtt_reconnect_short_wait_s() -> u64 {
60
}
fn default_mqtt_reconnect_long_wait_s() -> u64 {
3600
}
#[derive(Debug, Clone, Deserialize)]
pub struct Mqtt {
pub host: String,
@@ -53,6 +453,31 @@ pub struct Mqtt {
pub keepalive_s: u64,
pub qos: u8,
pub retain_states: bool,
#[serde(default)]
pub reconnect: MqttReconnect,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MqttReconnect {
#[serde(default = "default_mqtt_reconnect_attempts")]
pub attempts: u32,
#[serde(default = "default_mqtt_reconnect_retry_delay_s")]
pub retry_delay_s: u64,
#[serde(default = "default_mqtt_reconnect_short_wait_s")]
pub short_wait_s: u64,
#[serde(default = "default_mqtt_reconnect_long_wait_s")]
pub long_wait_s: u64,
}
impl Default for MqttReconnect {
fn default() -> Self {
Self {
attempts: default_mqtt_reconnect_attempts(),
retry_delay_s: default_mqtt_reconnect_retry_delay_s(),
short_wait_s: default_mqtt_reconnect_short_wait_s(),
long_wait_s: default_mqtt_reconnect_long_wait_s(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
@@ -64,7 +489,30 @@ pub struct Features {
#[derive(Debug, Clone, Deserialize)]
pub struct Telemetry {
pub enabled: bool,
#[serde(default = "default_telemetry_metrics")]
pub metrics: HashMap<String, TelemetryMetric>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TelemetryMetric {
#[serde(default = "default_metric_enabled")]
pub enabled: bool,
#[serde(default = "default_metric_discovery_enabled")]
pub discovery_enabled: bool,
#[serde(default = "default_metric_interval_s")]
pub interval_s: u64,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub unique_id: Option<String>,
#[serde(default)]
pub unit: Option<String>,
#[serde(default)]
pub device_class: Option<String>,
#[serde(default)]
pub state_class: Option<String>,
#[serde(default)]
pub icon: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
@@ -169,6 +617,19 @@ fn expand_variables(cfg: &mut Config) -> Result<()> {
cfg.mqtt.client_id = hostname.clone();
}
for metric in cfg.features.telemetry.metrics.values_mut() {
if let Some(name) = metric.name.as_mut() {
if name.contains("$hostname") {
*name = name.replace("$hostname", &hostname);
}
}
if let Some(unique_id) = metric.unique_id.as_mut() {
if unique_id.contains("$hostname") {
*unique_id = unique_id.replace("$hostname", &hostname);
}
}
}
Ok(())
}
@@ -186,6 +647,25 @@ fn validate(cfg: &Config) -> Result<()> {
if cfg.mqtt.discovery_prefix.trim().is_empty() {
bail!("mqtt.discovery_prefix must not be empty");
}
if cfg.mqtt.reconnect.attempts == 0 {
bail!("mqtt.reconnect.attempts must be > 0");
}
if cfg.mqtt.reconnect.retry_delay_s == 0 {
bail!("mqtt.reconnect.retry_delay_s must be > 0");
}
if cfg.mqtt.reconnect.short_wait_s == 0 {
bail!("mqtt.reconnect.short_wait_s must be > 0");
}
if cfg.mqtt.reconnect.long_wait_s == 0 {
bail!("mqtt.reconnect.long_wait_s must be > 0");
}
if cfg.features.telemetry.enabled {
for (name, metric) in &cfg.features.telemetry.metrics {
if metric.enabled && metric.interval_s == 0 {
bail!("telemetry.metrics.{}.interval_s must be > 0", name);
}
}
}
Ok(())
}
@@ -213,7 +693,10 @@ mqtt:
features:
telemetry:
enabled: true
interval_s: 5
metrics:
cpu_usage:
enabled: true
interval_s: 5
commands:
enabled: true
cooldown_s: 2
@@ -235,6 +718,7 @@ publish:
assert_eq!(cfg.device.name, "test");
assert_eq!(cfg.mqtt.port, 1883);
assert!(cfg.features.commands.dry_run);
assert!(cfg.features.telemetry.metrics.contains_key("cpu_usage"));
}
#[test]
@@ -257,7 +741,12 @@ mqtt:
features:
telemetry:
enabled: true
interval_s: 5
metrics:
cpu_usage:
enabled: true
interval_s: 5
name: "$hostname CPU Usage"
unique_id: "$hostname_cpu_usage"
commands:
enabled: true
cooldown_s: 2
@@ -276,7 +765,10 @@ publish:
let mut cfg: Config = serde_yaml::from_str(raw).unwrap();
expand_variables(&mut cfg).unwrap();
let hostname = get_hostname().unwrap();
let metric = cfg.features.telemetry.metrics.get("cpu_usage").unwrap();
assert_eq!(metric.unique_id.as_deref(), Some(&format!("{}_cpu_usage", hostname)));
assert_eq!(metric.name.as_deref(), Some(&format!("{} CPU Usage", hostname)));
let hostname = get_hostname().unwrap();
assert_eq!(cfg.device.name, hostname);
assert_eq!(cfg.device.identifiers[0], hostname);

View File

@@ -2,8 +2,10 @@
use anyhow::{Context, Result};
use rumqttc::AsyncClient;
use serde::Serialize;
use tracing::debug;
use crate::config::{base_device_topic, Config};
use crate::mqtt;
#[derive(Clone, Serialize)]
struct DeviceInfo {
@@ -17,26 +19,28 @@ struct DeviceInfo {
}
#[derive(Serialize)]
struct EntityConfig<'a> {
name: &'a str,
struct EntityConfig {
name: String,
unique_id: String,
state_topic: String,
availability_topic: String,
payload_available: &'a str,
payload_not_available: &'a str,
payload_available: String,
payload_not_available: String,
device: DeviceInfo,
#[serde(skip_serializing_if = "Option::is_none")]
command_topic: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
payload_on: Option<&'a str>,
payload_on: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
payload_off: Option<&'a str>,
payload_off: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
unit_of_measurement: Option<&'a str>,
unit_of_measurement: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
device_class: Option<&'a str>,
device_class: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<&'a str>,
state_class: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<String>,
}
// Publie les entites HA discovery pour les capteurs et commandes standard.
@@ -53,35 +57,38 @@ pub async fn publish_all(client: &AsyncClient, cfg: &Config) -> Result<()> {
};
let sensors = vec![
("cpu_usage", "CPU Usage", Some("%"), None, Some("mdi:chip")),
("memory_used_mb", "Memory Used", Some("MB"), None, Some("mdi:memory")),
("memory_total_mb", "Memory Total", Some("MB"), None, Some("mdi:memory")),
("ip_address", "IP Address", None, None, Some("mdi:ip")),
("power_state", "Power State", None, None, Some("mdi:power")),
("battery_level", "Battery Level", Some("%"), Some("battery"), Some("mdi:battery")),
("battery_state", "Battery State", None, None, Some("mdi:battery-charging")),
];
for (key, name, unit, class, icon) in sensors {
let entity_name = format!("{}_{}", key, cfg.device.name);
let entity = EntityConfig {
name: &entity_name,
unique_id: format!("{}_{}", cfg.device.name, key),
state_topic: format!("{}/{}", base, key),
availability_topic: format!("{}/{}/available", base, key),
payload_available: "online",
payload_not_available: "offline",
device: DeviceInfo { ..device.clone() },
command_topic: None,
payload_on: None,
payload_off: None,
unit_of_measurement: unit,
device_class: class,
icon,
};
let topic = format!("{}/sensor/{}/{}/config", prefix, cfg.device.name, entity_name);
publish_discovery(client, &topic, &entity).await?;
if cfg.features.telemetry.enabled {
for (key, metric) in &cfg.features.telemetry.metrics {
if !metric.enabled {
continue;
}
if !metric.discovery_enabled {
continue;
}
let name = normalize_optional(&metric.name)
.unwrap_or_else(|| format!("{}_{}", key, cfg.device.name));
let unique_id = normalize_optional(&metric.unique_id)
.unwrap_or_else(|| format!("{}_{}", cfg.device.name, key));
let entity = EntityConfig {
name,
unique_id,
state_topic: format!("{}/{}", base, key),
availability_topic: format!("{}/availability", base),
payload_available: "online".to_string(),
payload_not_available: "offline".to_string(),
device: DeviceInfo { ..device.clone() },
command_topic: None,
payload_on: None,
payload_off: None,
unit_of_measurement: normalize_unit(&metric.unit, &metric.device_class),
device_class: normalize_optional(&metric.device_class),
state_class: normalize_optional(&metric.state_class),
icon: normalize_optional(&metric.icon),
};
let entity_name = format!("{}_{}", key, cfg.device.name);
let topic = format!("{}/sensor/{}/{}/config", prefix, cfg.device.name, entity_name);
publish_discovery(client, &topic, &entity).await?;
}
}
let switches = vec![
@@ -91,22 +98,23 @@ pub async fn publish_all(client: &AsyncClient, cfg: &Config) -> Result<()> {
("screen", "Screen", "cmd/screen/set"),
];
for (key, name, cmd) in switches {
for (key, _name, cmd) in switches {
let entity_name = format!("{}_{}", key, cfg.device.name);
let entity = EntityConfig {
name: &entity_name,
name: entity_name.clone(),
unique_id: format!("{}_{}", cfg.device.name, key),
state_topic: format!("{}/{}/state", base, key),
availability_topic: format!("{}/{}/available", base, key),
payload_available: "online",
payload_not_available: "offline",
availability_topic: format!("{}/availability", base),
payload_available: "online".to_string(),
payload_not_available: "offline".to_string(),
device: DeviceInfo { ..device.clone() },
command_topic: Some(format!("{}/{}", base, cmd)),
payload_on: Some("ON"),
payload_off: Some("OFF"),
payload_on: Some("ON".to_string()),
payload_off: Some("OFF".to_string()),
unit_of_measurement: None,
device_class: Some("switch"),
icon: Some("mdi:power"),
device_class: Some("switch".to_string()),
state_class: None,
icon: Some("mdi:power".to_string()),
};
let topic = format!("{}/switch/{}/{}/config", prefix, cfg.device.name, entity_name);
publish_discovery(client, &topic, &entity).await?;
@@ -117,9 +125,29 @@ pub async fn publish_all(client: &AsyncClient, cfg: &Config) -> Result<()> {
async fn publish_discovery<T: Serialize>(client: &AsyncClient, topic: &str, payload: &T) -> Result<()> {
let data = serde_json::to_vec(payload).context("serialize discovery")?;
debug!(topic = %topic, bytes = data.len(), "publish discovery");
client
.publish(topic, rumqttc::QoS::AtLeastOnce, true, data)
.await
.context("publish discovery")?;
mqtt::record_publish();
Ok(())
}
fn normalize_optional(value: &Option<String>) -> Option<String> {
value
.as_ref()
.map(|item| item.trim())
.filter(|item| !item.is_empty())
.map(|item| item.to_string())
}
fn normalize_unit(unit: &Option<String>, device_class: &Option<String>) -> Option<String> {
let unit = normalize_optional(unit);
if matches!(normalize_optional(device_class).as_deref(), Some("temperature")) {
if matches!(unit.as_deref(), Some("C")) {
return Some("°C".to_string());
}
}
unit
}

View File

@@ -1,15 +1,16 @@
// Point d'entree principal de l'application.
use anyhow::Result;
use tracing::info;
use tracing_subscriber::EnvFilter;
use pilot_v2::config;
use pilot_v2::runtime::Runtime;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter("pilot_v2=info")
.init();
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("pilot_v2=info"));
tracing_subscriber::fmt().with_env_filter(filter).init();
let config = config::load()?;
info!("config loaded");

View File

@@ -3,6 +3,8 @@ use anyhow::{Context, Result};
use rumqttc::{AsyncClient, EventLoop, LastWill, MqttOptions, QoS};
use serde::Serialize;
use std::time::Duration;
use std::sync::atomic::{AtomicU64, Ordering};
use tracing::{debug, info};
use crate::config::{base_device_topic, Config};
@@ -11,6 +13,15 @@ pub struct MqttHandle {
pub event_loop: EventLoop,
}
static PUBLISH_COUNT: AtomicU64 = AtomicU64::new(0);
pub fn take_publish_count() -> u64 {
PUBLISH_COUNT.swap(0, Ordering::Relaxed)
}
pub fn record_publish() {
PUBLISH_COUNT.fetch_add(1, Ordering::Relaxed);
}
#[derive(Debug, Serialize)]
pub struct Status {
pub version: String,
@@ -58,12 +69,15 @@ pub fn connect(cfg: &Config) -> Result<MqttHandle> {
// Publie availability en retained pour indiquer online/offline.
pub async fn publish_availability(client: &AsyncClient, cfg: &Config, online: bool) -> Result<()> {
let topic = format!("{}/availability", base_device_topic(cfg));
let base = base_device_topic(cfg);
let payload = if online { "online" } else { "offline" };
let topic = format!("{}/availability", base);
info!(topic = %topic, payload = %payload, "publishing availability");
client
.publish(topic, qos(cfg), true, payload)
.await
.context("publish availability")?;
record_publish();
Ok(())
}
@@ -75,10 +89,12 @@ pub async fn publish_status(
) -> Result<()> {
let topic = format!("{}/status", base_device_topic(cfg));
let payload = serde_json::to_vec(status).context("serialize status")?;
debug!(topic = %topic, bytes = payload.len(), "publish status");
client
.publish(topic, qos(cfg), true, payload)
.await
.context("publish status")?;
record_publish();
Ok(())
}
@@ -90,10 +106,12 @@ pub async fn publish_capabilities(
) -> Result<()> {
let topic = format!("{}/capabilities", base_device_topic(cfg));
let payload = serde_json::to_vec(capabilities).context("serialize capabilities")?;
debug!(topic = %topic, bytes = payload.len(), "publish capabilities");
client
.publish(topic, qos(cfg), true, payload)
.await
.context("publish capabilities")?;
record_publish();
Ok(())
}
@@ -105,10 +123,12 @@ pub async fn publish_state(
value: &str,
) -> Result<()> {
let topic = format!("{}/{}", base_device_topic(cfg), name);
debug!(topic = %topic, payload = %value, "publish state");
client
.publish(topic, qos(cfg), cfg.mqtt.retain_states, value)
.await
.context("publish state")?;
record_publish();
Ok(())
}
@@ -120,16 +140,19 @@ pub async fn publish_switch_state(
value: &str,
) -> Result<()> {
let topic = format!("{}/{}/state", base_device_topic(cfg), name);
debug!(topic = %topic, payload = %value, "publish switch state");
client
.publish(topic, qos(cfg), cfg.mqtt.retain_states, value)
.await
.context("publish switch state")?;
record_publish();
Ok(())
}
// S'abonne aux commandes standard (cmd/<action>/set).
pub async fn subscribe_commands(client: &AsyncClient, cfg: &Config) -> Result<()> {
let topic = format!("{}/cmd/+/set", base_device_topic(cfg));
debug!(topic = %topic, "subscribe commands");
client
.subscribe(topic, qos(cfg))
.await

View File

@@ -1,5 +1,6 @@
// Implementations Linux (logind, sudoers, gnome busctl, x11 xset).
use anyhow::{bail, Context, Result};
use tracing::debug;
use std::process::Command;
use crate::commands::{CommandAction, CommandValue};
@@ -55,21 +56,35 @@ pub fn execute_screen(backend: &str, value: CommandValue) -> Result<()> {
),
},
"x11_xset" => match value {
CommandValue::Off => run("xset", &["dpms", "force", "off"]),
CommandValue::On => run("xset", &["dpms", "force", "on"]),
CommandValue::Off => {
log_x11_env();
run("xset", &["dpms", "force", "off"])
}
CommandValue::On => {
log_x11_env();
run("xset", &["dpms", "force", "on"])
}
},
_ => bail!("unknown linux screen backend"),
}
}
fn run(cmd: &str, args: &[&str]) -> Result<()> {
let status = Command::new(cmd)
debug!(%cmd, args = ?args, "running command");
let output = Command::new(cmd)
.args(args)
.status()
.output()
.with_context(|| format!("failed to run {cmd}"))?;
if status.success() {
if output.status.success() {
Ok(())
} else {
bail!("command failed: {cmd}")
let stderr = String::from_utf8_lossy(&output.stderr);
bail!("command failed: {cmd} ({}) {stderr}", output.status)
}
}
fn log_x11_env() {
let display_env = std::env::var("DISPLAY").unwrap_or_default();
let xauth_env = std::env::var("XAUTHORITY").unwrap_or_default();
debug!(display_env = %display_env, xauthority_env = %xauth_env, "x11 environment");
}

View File

@@ -1,10 +1,10 @@
// Ce module orchestre le cycle de vie de l'application.
use anyhow::Result;
use std::time::Instant;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::process::Command;
use tokio::time::{interval, sleep, Duration};
use tracing::warn;
use tracing::{debug, info, warn};
use crate::config::Config;
use crate::commands::{self, CommandAction, CommandValue};
@@ -18,6 +18,12 @@ pub struct Runtime {
start: Instant,
}
struct MetricSchedule {
name: String,
interval: Duration,
next_due: Instant,
}
impl Runtime {
// Cree un runtime avec la configuration chargee.
pub fn new(config: Config) -> Self {
@@ -34,38 +40,63 @@ impl Runtime {
let mut event_loop = handle.event_loop;
let client = handle.client;
// Wait for MQTT connection to be established
loop {
match event_loop.poll().await {
Ok(rumqttc::Event::Incoming(rumqttc::Packet::ConnAck(_))) => {
tracing::info!("mqtt connected");
break;
}
Ok(_) => continue,
Err(err) => {
tracing::warn!(error = %err, "mqtt connection error, retrying...");
sleep(Duration::from_secs(2)).await;
}
}
}
wait_for_mqtt_connection(&mut event_loop, &self.config).await;
// Spawn event loop handler in background to process messages
let (cmd_tx, mut cmd_rx) = tokio::sync::mpsc::unbounded_channel();
tokio::spawn(async move {
let mut backoff = Duration::from_secs(1);
loop {
match event_loop.poll().await {
Ok(rumqttc::Event::Incoming(rumqttc::Packet::Publish(publish))) => {
let payload = String::from_utf8_lossy(&publish.payload);
debug!(topic = %publish.topic, payload = %payload, "mqtt incoming publish");
let _ = cmd_tx.send((publish.topic.to_string(), publish.payload.to_vec()));
backoff = Duration::from_secs(1);
}
Ok(_) => {
backoff = Duration::from_secs(1);
}
Ok(_) => {}
Err(err) => {
tracing::warn!(error = %err, "mqtt eventloop error");
tokio::time::sleep(Duration::from_secs(2)).await;
tokio::time::sleep(backoff).await;
backoff = std::cmp::min(backoff * 2, Duration::from_secs(60));
}
}
}
});
let enabled_metrics: HashSet<String> = if self.config.features.telemetry.enabled {
self.config
.features
.telemetry
.metrics
.iter()
.filter_map(|(name, metric)| {
if metric.enabled {
Some(name.clone())
} else {
None
}
})
.collect()
} else {
HashSet::new()
};
let mut metric_schedule = Vec::new();
if self.config.features.telemetry.enabled {
for (name, metric) in &self.config.features.telemetry.metrics {
if !metric.enabled {
continue;
}
metric_schedule.push(MetricSchedule {
name: name.clone(),
interval: Duration::from_secs(metric.interval_s),
next_due: Instant::now(),
});
}
}
// Send initial messages
if self.config.publish.availability {
mqtt::publish_availability(&client, &self.config, true).await?;
@@ -81,11 +112,6 @@ impl Runtime {
publish_initial_command_states(&client, &self.config).await;
let initial_power_state = detect_power_state();
if let Err(err) = mqtt::publish_state(&client, &self.config, "power_state", &initial_power_state).await {
warn!(error = %err, "publish power_state failed");
}
if self.config.features.commands.enabled {
mqtt::subscribe_commands(&client, &self.config).await?;
}
@@ -97,12 +123,11 @@ impl Runtime {
} else {
None
};
let mut telemetry_tick = interval(Duration::from_secs(
self.config.features.telemetry.interval_s,
));
let mut telemetry_tick = interval(Duration::from_secs(1));
let mut heartbeat_tick = interval(Duration::from_secs(
self.config.publish.heartbeat_s,
));
let mut stats_tick = interval(Duration::from_secs(60));
let mut last_exec: HashMap<CommandAction, std::time::Instant> = HashMap::new();
let shutdown = tokio::signal::ctrl_c();
@@ -111,24 +136,47 @@ impl Runtime {
loop {
tokio::select! {
_ = telemetry_tick.tick(), if telemetry.is_some() => {
let metrics = telemetry.as_mut().unwrap().read();
for (name, value) in metrics {
if let Err(err) = mqtt::publish_state(&client, &self.config, &name, &value).await {
warn!(error = %err, "publish state failed");
if metric_schedule.is_empty() {
continue;
}
let now = Instant::now();
let mut due: HashSet<String> = HashSet::new();
for schedule in &mut metric_schedule {
if now >= schedule.next_due {
due.insert(schedule.name.clone());
schedule.next_due = now + schedule.interval;
}
}
if !due.is_empty() {
let power_state_due = due.remove("power_state");
let metrics = telemetry.as_mut().unwrap().read(&due);
for (name, value) in metrics {
if let Err(err) = mqtt::publish_state(&client, &self.config, &name, &value).await {
warn!(error = %err, "publish state failed");
}
}
if power_state_due && enabled_metrics.contains("power_state") {
let current = detect_power_state();
if let Err(err) = mqtt::publish_state(&client, &self.config, "power_state", &current).await {
warn!(error = %err, "publish power_state failed");
}
}
}
}
_ = heartbeat_tick.tick() => {
let current = detect_power_state();
if let Err(err) = mqtt::publish_state(&client, &self.config, "power_state", &current).await {
warn!(error = %err, "publish power_state failed");
}
let status = build_status(&self.config, self.start.elapsed().as_secs());
if let Err(err) = mqtt::publish_status(&client, &self.config, &status).await {
warn!(error = %err, "publish status failed");
}
}
_ = stats_tick.tick() => {
let published = mqtt::take_publish_count();
info!(count = published, "mqtt publish stats (last 60s)");
}
Some((topic, payload)) = cmd_rx.recv() => {
let payload_str = String::from_utf8_lossy(&payload);
debug!(%topic, payload = %payload_str, "command received from mqtt");
if let Err(err) = handle_command(
&client,
&self.config,
@@ -153,13 +201,59 @@ impl Runtime {
}
}
async fn wait_for_mqtt_connection(event_loop: &mut rumqttc::EventLoop, cfg: &Config) {
let policy = &cfg.mqtt.reconnect;
let retry_delay = Duration::from_secs(policy.retry_delay_s);
let short_wait = Duration::from_secs(policy.short_wait_s);
let long_wait = Duration::from_secs(policy.long_wait_s);
let mut attempts = 0u32;
let mut used_short_wait = false;
loop {
match event_loop.poll().await {
Ok(rumqttc::Event::Incoming(rumqttc::Packet::ConnAck(_))) => {
info!("mqtt connected");
break;
}
Ok(_) => continue,
Err(err) => {
attempts = attempts.saturating_add(1);
warn!(error = %err, attempt = attempts, max_attempts = policy.attempts, "mqtt connection error");
if attempts < policy.attempts {
sleep(retry_delay).await;
continue;
}
attempts = 0;
if !used_short_wait {
info!(
wait_s = policy.short_wait_s,
"mqtt unavailable, waiting before next retry batch"
);
sleep(short_wait).await;
used_short_wait = true;
} else {
info!(
wait_s = policy.long_wait_s,
"mqtt unavailable, waiting longer before next retry batch"
);
sleep(long_wait).await;
}
}
}
}
}
// Genere les capacites declarees par le programme.
fn capabilities(cfg: &Config) -> Capabilities {
let mut telemetry = Vec::new();
if cfg.features.telemetry.enabled {
telemetry.push("cpu_usage".to_string());
telemetry.push("cpu_temp".to_string());
telemetry.push("memory".to_string());
telemetry = cfg
.features
.telemetry
.metrics
.iter()
.filter_map(|(name, metric)| if metric.enabled { Some(name.clone()) } else { None })
.collect();
}
let mut commands = Vec::new();
@@ -262,6 +356,7 @@ async fn handle_command(
) -> anyhow::Result<()> {
let action = commands::parse_action(topic)?;
let value = commands::parse_value(payload)?;
debug!(%topic, ?action, ?value, "command received");
if !commands::allowlist_allows(&cfg.features.commands.allowlist, action) {
return Ok(());
@@ -300,7 +395,9 @@ async fn handle_command(
}
}
CommandAction::Screen => {
platform::execute_screen(&backend_screen(cfg), value)?;
let backend = backend_screen(cfg);
debug!(backend = %backend, ?value, "executing screen command");
platform::execute_screen(&backend, value)?;
publish_command_state(client, cfg, action, value).await?;
}
}

View File

@@ -1,19 +1,22 @@
// Ce module declare l'interface de telemetrie et une implementation basique.
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::Path;
use std::process::Command;
use local_ip_address::local_ip;
use sysinfo::System;
use sysinfo::{Components, Disks, System};
// Retourne un dictionnaire simple {nom -> valeur} pour les capteurs.
pub trait TelemetryProvider {
fn read(&mut self) -> HashMap<String, String>;
fn read(&mut self, metrics: &HashSet<String>) -> HashMap<String, String>;
}
// Telemetrie basique cross-platform (cpu, memoire, ip, batterie).
pub struct BasicTelemetry {
system: System,
disks: Disks,
components: Components,
}
impl BasicTelemetry {
@@ -21,32 +24,156 @@ impl BasicTelemetry {
pub fn new() -> Self {
let mut system = System::new();
system.refresh_all();
Self { system }
let disks = Disks::new_with_refreshed_list();
let components = Components::new_with_refreshed_list();
Self {
system,
disks,
components,
}
}
}
impl TelemetryProvider for BasicTelemetry {
fn read(&mut self) -> HashMap<String, String> {
self.system.refresh_cpu();
self.system.refresh_memory();
fn read(&mut self, metrics: &HashSet<String>) -> HashMap<String, String> {
let mut values = HashMap::new();
let cpu = self.system.global_cpu_info().cpu_usage();
let mem_used_mb = self.system.used_memory() / 1024;
let mem_total_mb = self.system.total_memory() / 1024;
values.insert("cpu_usage".to_string(), format!("{:.1}", cpu));
values.insert("memory_used_mb".to_string(), mem_used_mb.to_string());
values.insert("memory_total_mb".to_string(), mem_total_mb.to_string());
if let Ok(ip) = local_ip() {
values.insert("ip_address".to_string(), ip.to_string());
if metrics.contains("cpu_usage") {
self.system.refresh_cpu();
let cpu = self.system.global_cpu_info().cpu_usage();
values.insert("cpu_usage".to_string(), format!("{:.1}", cpu));
}
// Add battery info if available
if let Some(battery) = read_battery_info() {
values.insert("battery_level".to_string(), battery.level.to_string());
values.insert("battery_state".to_string(), battery.state);
if metrics.contains("memory_used_gb") || metrics.contains("memory_total_gb") {
self.system.refresh_memory();
let mem_used_gb = self.system.used_memory() as f64 / 1024.0 / 1024.0 / 1024.0;
let mem_total_gb = self.system.total_memory() as f64 / 1024.0 / 1024.0 / 1024.0;
if metrics.contains("memory_used_gb") {
values.insert("memory_used_gb".to_string(), format!("{:.2}", mem_used_gb));
}
if metrics.contains("memory_total_gb") {
values.insert("memory_total_gb".to_string(), format!("{:.2}", mem_total_gb));
}
}
if metrics.contains("ip_address") {
if let Ok(ip) = local_ip() {
values.insert("ip_address".to_string(), ip.to_string());
}
}
let wants_pilot_cpu = metrics.contains("pilot_v2_cpu_usage");
let wants_pilot_mem = metrics.contains("pilot_v2_mem_used_mb");
if wants_pilot_cpu || wants_pilot_mem {
self.system.refresh_processes();
let (cpu_usage, mem_used_mb) =
read_process_stats(&self.system, &["pilot-v2", "pilot_v2"]);
if wants_pilot_cpu {
values.insert("pilot_v2_cpu_usage".to_string(), format!("{:.1}", cpu_usage));
}
if wants_pilot_mem {
values.insert("pilot_v2_mem_used_mb".to_string(), format!("{:.2}", mem_used_mb));
}
}
if metrics.contains("kernel") || metrics.contains("kernel_version") {
if let Some(kernel) = System::kernel_version() {
if metrics.contains("kernel") {
values.insert("kernel".to_string(), kernel.clone());
}
if metrics.contains("kernel_version") {
values.insert("kernel_version".to_string(), kernel);
}
}
}
if metrics.contains("os_version") || metrics.contains("os") {
let os = System::long_os_version().or_else(System::os_version);
if let Some(version) = os {
if metrics.contains("os_version") {
values.insert("os_version".to_string(), version.clone());
}
if metrics.contains("os") {
values.insert("os".to_string(), version);
}
}
}
let wants_cpu_temp = metrics.contains("cpu_temp_c");
let wants_ssd_temp = metrics.contains("ssd_temp_c");
let wants_amd_gpu_temp = metrics.contains("amd_gpu_temp_c");
if wants_cpu_temp || wants_ssd_temp || wants_amd_gpu_temp {
self.components.refresh();
if wants_cpu_temp {
if let Some(temp) = read_component_temp(&self.components, &["cpu", "package", "tctl", "core"]) {
values.insert("cpu_temp_c".to_string(), format!("{:.1}", temp));
}
}
if wants_ssd_temp {
if let Some(temp) = read_component_temp(&self.components, &["nvme", "ssd", "disk", "drive"]) {
values.insert("ssd_temp_c".to_string(), format!("{:.1}", temp));
}
}
if wants_amd_gpu_temp {
if let Some(temp) = read_component_temp(&self.components, &["amdgpu", "edge"]) {
values.insert("amd_gpu_temp_c".to_string(), format!("{:.1}", temp));
}
}
}
let wants_fan_cpu = metrics.contains("fan_cpu_rpm");
let wants_fan_gpu = metrics.contains("fan_gpu_rpm");
if wants_fan_cpu {
if let Some(rpm) = read_fan_rpm(&["cpu"]) {
values.insert("fan_cpu_rpm".to_string(), rpm.to_string());
}
}
if wants_fan_gpu {
if let Some(rpm) = read_fan_rpm(&["gpu", "vga"]) {
values.insert("fan_gpu_rpm".to_string(), rpm.to_string());
}
}
if metrics.contains("disk_free_gb") {
self.disks.refresh();
if let Some(free_gb) = read_disk_free_gb(&self.disks) {
values.insert("disk_free_gb".to_string(), format!("{:.2}", free_gb));
}
}
if metrics.contains("amd_gpu_usage") {
if let Some(usage) = read_amd_gpu_busy_percent() {
values.insert("amd_gpu_usage".to_string(), usage.to_string());
}
}
if metrics.contains("amd_gpu_mem_used_gb") {
if let Some(mem_gb) = read_amd_gpu_mem_used_gb() {
values.insert("amd_gpu_mem_used_gb".to_string(), format!("{:.2}", mem_gb));
}
}
if wants_gpu_metrics(metrics) {
if let Some(stats) = read_nvidia_gpu_stats() {
if metrics.contains("gpu_usage") {
let avg = stats.iter().map(|item| item.utilization as u64).sum::<u64>() as f64
/ stats.len() as f64;
values.insert("gpu_usage".to_string(), format!("{:.1}", avg));
}
insert_gpu_metric(&mut values, metrics, &stats, 0);
insert_gpu_metric(&mut values, metrics, &stats, 1);
}
}
if metrics.contains("battery_level") || metrics.contains("battery_state") {
if let Some(battery) = read_battery_info() {
if metrics.contains("battery_level") {
values.insert("battery_level".to_string(), battery.level.to_string());
}
if metrics.contains("battery_state") {
values.insert("battery_state".to_string(), battery.state);
}
}
}
values
@@ -126,7 +253,315 @@ fn read_battery_linux() -> Option<BatteryInfo> {
#[cfg(target_os = "windows")]
fn read_battery_windows() -> Option<BatteryInfo> {
// TODO: Implement Windows battery reading via GetSystemPowerStatus
// For now, return None
None
use windows_sys::Win32::System::Power::{GetSystemPowerStatus, SYSTEM_POWER_STATUS};
let mut status = SYSTEM_POWER_STATUS {
ACLineStatus: 0,
BatteryFlag: 0,
BatteryLifePercent: 0,
SystemStatusFlag: 0,
BatteryLifeTime: 0,
BatteryFullLifeTime: 0,
};
let ok = unsafe { GetSystemPowerStatus(&mut status as *mut _) };
if ok == 0 {
return None;
}
if status.BatteryFlag == 0x80 || status.BatteryLifePercent == 255 {
return None;
}
let level = status.BatteryLifePercent as u8;
let state = if status.ACLineStatus == 1 {
if level >= 100 {
"full"
} else if (status.BatteryFlag & 0x08) != 0 {
"charging"
} else {
"not_charging"
}
} else if status.ACLineStatus == 0 {
"discharging"
} else {
"unknown"
};
Some(BatteryInfo {
level,
state: state.to_string(),
})
}
fn read_disk_free_gb(disks: &Disks) -> Option<f64> {
let free_bytes: u64 = disks
.list()
.iter()
.filter(|disk| !disk.is_removable())
.map(|disk| disk.available_space())
.sum();
if free_bytes == 0 {
return None;
}
Some(free_bytes as f64 / 1024.0 / 1024.0 / 1024.0)
}
fn read_fan_rpm(keywords: &[&str]) -> Option<u64> {
#[cfg(target_os = "linux")]
{
read_fan_rpm_linux(keywords)
}
#[cfg(not(target_os = "linux"))]
{
let _ = keywords;
None
}
}
#[cfg(target_os = "linux")]
fn read_fan_rpm_linux(keywords: &[&str]) -> Option<u64> {
let hwmon_path = Path::new("/sys/class/hwmon");
if !hwmon_path.exists() {
return None;
}
let mut best: Option<u64> = None;
let keywords: Vec<String> = keywords.iter().map(|item| item.to_lowercase()).collect();
for entry in fs::read_dir(hwmon_path).ok()?.flatten() {
let path = entry.path();
let hwmon_name = fs::read_to_string(path.join("name"))
.unwrap_or_default()
.to_lowercase();
let entries = match fs::read_dir(&path) {
Ok(entries) => entries,
Err(_) => continue,
};
for file in entries.flatten() {
let file_name = file.file_name();
let file_name = file_name.to_string_lossy();
if !file_name.starts_with("fan") || !file_name.ends_with("_input") {
continue;
}
let rpm = match fs::read_to_string(file.path()) {
Ok(value) => match value.trim().parse::<u64>() {
Ok(value) => value,
Err(_) => continue,
},
Err(_) => continue,
};
let label_path = path.join(file_name.replace("_input", "_label"));
let label = fs::read_to_string(label_path)
.unwrap_or_default()
.to_lowercase();
let haystack = format!("{} {}", hwmon_name, label);
let matched = keywords.iter().any(|key| haystack.contains(key));
if matched {
best = Some(best.map_or(rpm, |current| current.max(rpm)));
}
}
}
best
}
fn wants_gpu_metrics(metrics: &HashSet<String>) -> bool {
metrics.contains("gpu_usage")
|| metrics.contains("gpu0_usage")
|| metrics.contains("gpu1_usage")
|| metrics.contains("gpu0_mem_used_gb")
|| metrics.contains("gpu1_mem_used_gb")
|| metrics.contains("gpu0_temp_c")
|| metrics.contains("gpu1_temp_c")
}
#[derive(Debug)]
struct NvidiaGpuStats {
utilization: u8,
memory_used_mb: u64,
temperature_c: u32,
}
fn read_nvidia_gpu_stats() -> Option<Vec<NvidiaGpuStats>> {
let output = Command::new("nvidia-smi")
.args([
"--query-gpu=utilization.gpu,memory.used,temperature.gpu",
"--format=csv,noheader,nounits",
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut stats = Vec::new();
for line in stdout.lines() {
let parts: Vec<&str> = line.split(',').map(|part| part.trim()).collect();
if parts.len() != 3 {
continue;
}
let utilization: u8 = parts[0].parse().ok()?;
let memory_used_mb: u64 = parts[1].parse().ok()?;
let temperature_c: u32 = parts[2].parse().ok()?;
stats.push(NvidiaGpuStats {
utilization,
memory_used_mb,
temperature_c,
});
}
if stats.is_empty() {
None
} else {
Some(stats)
}
}
fn insert_gpu_metric(
values: &mut HashMap<String, String>,
metrics: &HashSet<String>,
stats: &[NvidiaGpuStats],
index: usize,
) {
if stats.len() <= index {
return;
}
let gpu = &stats[index];
let usage_key = format!("gpu{}_usage", index);
if metrics.contains(&usage_key) {
values.insert(usage_key, gpu.utilization.to_string());
}
let mem_key = format!("gpu{}_mem_used_gb", index);
if metrics.contains(&mem_key) {
let mem_gb = gpu.memory_used_mb as f64 / 1024.0;
values.insert(mem_key, format!("{:.2}", mem_gb));
}
let temp_key = format!("gpu{}_temp_c", index);
if metrics.contains(&temp_key) {
values.insert(temp_key, gpu.temperature_c.to_string());
}
}
fn read_component_temp(components: &Components, keywords: &[&str]) -> Option<f32> {
let mut best: Option<f32> = None;
for component in components.list() {
let label = component.label().to_lowercase();
if !keywords.iter().any(|key| label.contains(key)) {
continue;
}
let temp = component.temperature();
if temp.is_nan() {
continue;
}
best = Some(best.map_or(temp, |current| current.max(temp)));
}
best
}
fn read_process_stats(system: &System, names: &[&str]) -> (f32, f64) {
let mut cpu_total = 0.0_f32;
let mut mem_total = 0_u64;
let names: Vec<String> = names.iter().map(|name| name.to_lowercase()).collect();
for process in system.processes().values() {
let pname = process.name().to_lowercase();
if names.iter().any(|name| pname == *name) {
cpu_total += process.cpu_usage();
mem_total = mem_total.saturating_add(process.memory());
}
}
let mem_mb = mem_total as f64 / 1024.0 / 1024.0;
(cpu_total, mem_mb)
}
fn read_amd_gpu_busy_percent() -> Option<u8> {
#[cfg(target_os = "linux")]
{
read_sysfs_amd_gpu_busy()
}
#[cfg(not(target_os = "linux"))]
{
None
}
}
fn read_amd_gpu_mem_used_gb() -> Option<f64> {
#[cfg(target_os = "linux")]
{
read_sysfs_amd_gpu_mem_used_gb()
}
#[cfg(not(target_os = "linux"))]
{
None
}
}
#[cfg(target_os = "linux")]
fn read_sysfs_amd_gpu_busy() -> Option<u8> {
let drm_path = Path::new("/sys/class/drm");
if !drm_path.exists() {
return None;
}
let mut best: Option<u8> = None;
for entry in fs::read_dir(drm_path).ok()?.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if !name.starts_with("card") {
continue;
}
let busy_path = entry.path().join("device/gpu_busy_percent");
let value = match fs::read_to_string(busy_path) {
Ok(value) => value.trim().parse::<u8>().ok(),
Err(_) => None,
};
if let Some(value) = value {
best = Some(best.map_or(value, |current| current.max(value)));
}
}
best
}
#[cfg(target_os = "linux")]
fn read_sysfs_amd_gpu_mem_used_gb() -> Option<f64> {
let drm_path = Path::new("/sys/class/drm");
if !drm_path.exists() {
return None;
}
let mut best: Option<u64> = None;
for entry in fs::read_dir(drm_path).ok()?.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if !name.starts_with("card") {
continue;
}
let mem_path = entry.path().join("device/mem_info_vram_used");
let value = match fs::read_to_string(mem_path) {
Ok(value) => value.trim().parse::<u64>().ok(),
Err(_) => None,
};
if let Some(value) = value {
best = Some(best.map_or(value, |current| current.max(value)));
}
}
best.map(|bytes| bytes as f64 / 1024.0 / 1024.0 / 1024.0)
}

View File

@@ -1 +1 @@
{"rustc_fingerprint":14304282315022827685,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.92.0 (ded5c06cf 2025-12-08)\nbinary: rustc\ncommit-hash: ded5c06cf21d2b93bffd5d884aa6e96934ee4234\ncommit-date: 2025-12-08\nhost: x86_64-unknown-linux-gnu\nrelease: 1.92.0\nLLVM version: 21.1.3\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/gilles/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":6593852017318555587,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.92.0 (ded5c06cf 2025-12-08)\nbinary: rustc\ncommit-hash: ded5c06cf21d2b93bffd5d884aa6e96934ee4234\ncommit-date: 2025-12-08\nhost: x86_64-unknown-linux-gnu\nrelease: 1.92.0\nLLVM version: 21.1.3\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/gilles/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
85dcbef30b3c5ce8

View File

@@ -0,0 +1 @@
{"rustc":4758242423518056681,"features":"[\"default\"]","declared_features":"[\"default\", \"set\"]","target":12410617755549227441,"profile":15657897354478470176,"path":3902012681159370491,"deps":[[7667230146095136825,"cfg_if",false,4127000677558031520],[8730874933663560167,"libc",false,1510286074041156629]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/hostname-fed00c17dd6a87b1/dep-lib-hostname","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View File

@@ -0,0 +1 @@
ddb1f574eee0ec70

View File

@@ -0,0 +1 @@
{"rustc":4758242423518056681,"features":"[]","declared_features":"[]","target":18319852477236491692,"profile":8731458305071235362,"path":4942398508502643691,"deps":[[1852463361802237065,"anyhow",false,3065966860047593048],[7720834239451334583,"tokio",false,621317579489071575],[8008191657135824715,"thiserror",false,8134831117750486109],[9146728503810466809,"local_ip_address",false,580179989201927340],[9397497715544097920,"pilot_v2",false,3800541833483423688],[9614479274285663593,"serde_yaml",false,17010506011042989064],[9963614578868468249,"sysinfo",false,1126872645888205972],[10148569567424105397,"rumqttc",false,14627974774119481560],[10992805584811413946,"serde_json",false,11032433416495698906],[11927239882567217773,"hostname",false,16743323536688340101],[12041186341109472307,"tracing_subscriber",false,3820729270548605713],[13548984313718623784,"serde",false,1571628319108191229],[14757622794040968908,"tracing",false,945257464964974167],[17916568863929494805,"zbus",false,5994297596113249579]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/pilot-v2-25f0ed7b6c4aef3a/dep-bin-pilot-v2","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
c88fca52a93cbe34

View File

@@ -0,0 +1 @@
{"rustc":4758242423518056681,"features":"[]","declared_features":"[]","target":5141203279786136790,"profile":8731458305071235362,"path":10763286916239946207,"deps":[[1852463361802237065,"anyhow",false,3065966860047593048],[7720834239451334583,"tokio",false,621317579489071575],[8008191657135824715,"thiserror",false,8134831117750486109],[9146728503810466809,"local_ip_address",false,580179989201927340],[9614479274285663593,"serde_yaml",false,17010506011042989064],[9963614578868468249,"sysinfo",false,1126872645888205972],[10148569567424105397,"rumqttc",false,14627974774119481560],[10992805584811413946,"serde_json",false,11032433416495698906],[11927239882567217773,"hostname",false,16743323536688340101],[12041186341109472307,"tracing_subscriber",false,3820729270548605713],[13548984313718623784,"serde",false,1571628319108191229],[14757622794040968908,"tracing",false,945257464964974167],[17916568863929494805,"zbus",false,5994297596113249579]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/pilot-v2-7da0affa3f0bcf3d/dep-lib-pilot_v2","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
667479e6e17bd807

View File

@@ -0,0 +1 @@
{"rustc":4758242423518056681,"features":"[]","declared_features":"[]","target":5141203279786136790,"profile":1722584277633009122,"path":10763286916239946207,"deps":[[1852463361802237065,"anyhow",false,3065966860047593048],[7720834239451334583,"tokio",false,621317579489071575],[8008191657135824715,"thiserror",false,8134831117750486109],[9146728503810466809,"local_ip_address",false,580179989201927340],[9614479274285663593,"serde_yaml",false,17010506011042989064],[9963614578868468249,"sysinfo",false,1126872645888205972],[10148569567424105397,"rumqttc",false,14627974774119481560],[10992805584811413946,"serde_json",false,11032433416495698906],[11927239882567217773,"hostname",false,16743323536688340101],[12041186341109472307,"tracing_subscriber",false,3820729270548605713],[13548984313718623784,"serde",false,1571628319108191229],[14757622794040968908,"tracing",false,945257464964974167],[17916568863929494805,"zbus",false,5994297596113249579]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/pilot-v2-b0e970ea03f661d3/dep-test-lib-pilot_v2","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
20230cb900da1e2d

View File

@@ -0,0 +1 @@
{"rustc":4758242423518056681,"features":"[]","declared_features":"[]","target":18319852477236491692,"profile":1722584277633009122,"path":4942398508502643691,"deps":[[1852463361802237065,"anyhow",false,3065966860047593048],[7720834239451334583,"tokio",false,621317579489071575],[8008191657135824715,"thiserror",false,8134831117750486109],[9146728503810466809,"local_ip_address",false,580179989201927340],[9397497715544097920,"pilot_v2",false,3800541833483423688],[9614479274285663593,"serde_yaml",false,17010506011042989064],[9963614578868468249,"sysinfo",false,1126872645888205972],[10148569567424105397,"rumqttc",false,14627974774119481560],[10992805584811413946,"serde_json",false,11032433416495698906],[11927239882567217773,"hostname",false,16743323536688340101],[12041186341109472307,"tracing_subscriber",false,3820729270548605713],[13548984313718623784,"serde",false,1571628319108191229],[14757622794040968908,"tracing",false,945257464964974167],[17916568863929494805,"zbus",false,5994297596113249579]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/pilot-v2-b15e3557562b4c91/dep-test-bin-pilot-v2","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View File

@@ -1 +1 @@
a2044ec8fbb9ee8f
56e4e5071a864e58

View File

@@ -1 +1 @@
{"rustc":4758242423518056681,"features":"[\"alloc\", \"default\", \"dev_urandom_fallback\"]","declared_features":"[\"alloc\", \"default\", \"dev_urandom_fallback\", \"less-safe-getrandom-custom-or-rdrand\", \"less-safe-getrandom-espidf\", \"slow_tests\", \"std\", \"test_logging\", \"unstable-testing-arm-no-hw\", \"unstable-testing-arm-no-neon\", \"wasm32_unknown_unknown_js\"]","target":13947150742743679355,"profile":15657897354478470176,"path":1700047507616561821,"deps":[[5491919304041016563,"build_script_build",false,17719473432237023447],[7667230146095136825,"cfg_if",false,4127000677558031520],[8995469080876806959,"untrusted",false,2560156330519654396],[9920160576179037441,"getrandom",false,28106126672913047]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/ring-cf7ffe450e0bb244/dep-lib-ring","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
{"rustc":4758242423518056681,"features":"[\"alloc\", \"default\", \"dev_urandom_fallback\"]","declared_features":"[\"alloc\", \"default\", \"dev_urandom_fallback\", \"less-safe-getrandom-custom-or-rdrand\", \"less-safe-getrandom-espidf\", \"slow_tests\", \"std\", \"test_logging\", \"unstable-testing-arm-no-hw\", \"unstable-testing-arm-no-neon\", \"wasm32_unknown_unknown_js\"]","target":13947150742743679355,"profile":15657897354478470176,"path":1700047507616561821,"deps":[[5491919304041016563,"build_script_build",false,7514885491771117960],[7667230146095136825,"cfg_if",false,4127000677558031520],[8995469080876806959,"untrusted",false,2560156330519654396],[9920160576179037441,"getrandom",false,28106126672913047]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/ring-cf7ffe450e0bb244/dep-lib-ring","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View File

@@ -1 +1 @@
0a7772e7b22e1a21
d840c4028e0101cb

View File

@@ -1 +1 @@
{"rustc":4758242423518056681,"features":"[\"default\", \"use-rustls\"]","declared_features":"[\"default\", \"proxy\", \"url\", \"use-native-tls\", \"use-rustls\", \"websocket\"]","target":4013803430344660022,"profile":15657897354478470176,"path":5809087934789344443,"deps":[[4656928804077918400,"flume",false,3373741850536154208],[6286470095574232244,"rustls_native_certs",false,8559626168360638579],[6355489020061627772,"bytes",false,9815496262182911704],[7720834239451334583,"tokio",false,621317579489071575],[8008191657135824715,"thiserror",false,8134831117750486109],[10629569228670356391,"futures_util",false,16904912441975097711],[10630857666389190470,"log",false,12461827721875662956],[12989347533245466967,"webpki",false,16007252436462410064],[15032952994102373905,"rustls_pemfile",false,3884589192664292971],[16357106084213134330,"tokio_rustls",false,12148791233613271013]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rumqttc-8625a624a03a2fb2/dep-lib-rumqttc","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
{"rustc":4758242423518056681,"features":"[\"default\", \"use-rustls\"]","declared_features":"[\"default\", \"proxy\", \"url\", \"use-native-tls\", \"use-rustls\", \"websocket\"]","target":4013803430344660022,"profile":15657897354478470176,"path":5809087934789344443,"deps":[[4656928804077918400,"flume",false,3373741850536154208],[6286470095574232244,"rustls_native_certs",false,8559626168360638579],[6355489020061627772,"bytes",false,9815496262182911704],[7720834239451334583,"tokio",false,621317579489071575],[8008191657135824715,"thiserror",false,8134831117750486109],[10629569228670356391,"futures_util",false,16904912441975097711],[10630857666389190470,"log",false,12461827721875662956],[12989347533245466967,"webpki",false,24339240726498268],[15032952994102373905,"rustls_pemfile",false,3884589192664292971],[16357106084213134330,"tokio_rustls",false,9680782312479827620]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rumqttc-8625a624a03a2fb2/dep-lib-rumqttc","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View File

@@ -1 +1 @@
{"rustc":4758242423518056681,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[5491919304041016563,"build_script_build",false,17719473432237023447],[17020669599254637850,"build_script_build",false,2470072584972325006]],"local":[{"Precalculated":"0.22.4"}],"rustflags":[],"config":0,"compile_kind":0}
{"rustc":4758242423518056681,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[5491919304041016563,"build_script_build",false,7514885491771117960],[17020669599254637850,"build_script_build",false,2470072584972325006]],"local":[{"Precalculated":"0.22.4"}],"rustflags":[],"config":0,"compile_kind":0}

View File

@@ -1 +1 @@
8e3e7cfa9196255f
67b3b0619188a8a5

View File

@@ -1 +1 @@
{"rustc":4758242423518056681,"features":"[\"log\", \"logging\", \"ring\", \"tls12\"]","declared_features":"[\"aws_lc_rs\", \"default\", \"log\", \"logging\", \"read_buf\", \"ring\", \"rustversion\", \"tls12\"]","target":4244986261372225136,"profile":15657897354478470176,"path":8140242195314691117,"deps":[[64645024058175247,"pki_types",false,4939645483297780558],[5491919304041016563,"ring",false,10371431482929317026],[10630857666389190470,"log",false,12461827721875662956],[12865141776541797048,"zeroize",false,5857433854117357238],[12989347533245466967,"webpki",false,16007252436462410064],[17003143334332120809,"subtle",false,10888626421743571120],[17020669599254637850,"build_script_build",false,8309930981967883158]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rustls-cf329e8beb2bec46/dep-lib-rustls","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
{"rustc":4758242423518056681,"features":"[\"log\", \"logging\", \"ring\", \"tls12\"]","declared_features":"[\"aws_lc_rs\", \"default\", \"log\", \"logging\", \"read_buf\", \"ring\", \"rustversion\", \"tls12\"]","target":4244986261372225136,"profile":15657897354478470176,"path":8140242195314691117,"deps":[[64645024058175247,"pki_types",false,4939645483297780558],[5491919304041016563,"ring",false,6363170769880867926],[10630857666389190470,"log",false,12461827721875662956],[12865141776541797048,"zeroize",false,5857433854117357238],[12989347533245466967,"webpki",false,24339240726498268],[17003143334332120809,"subtle",false,10888626421743571120],[17020669599254637850,"build_script_build",false,14039156543573013898]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rustls-cf329e8beb2bec46/dep-lib-rustls","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View File

@@ -1 +1 @@
501959e9472f25de
dc0b9b1569785600

View File

@@ -1 +1 @@
{"rustc":4758242423518056681,"features":"[\"alloc\", \"default\", \"ring\", \"std\"]","declared_features":"[\"alloc\", \"aws_lc_rs\", \"default\", \"ring\", \"std\"]","target":5054897795206437336,"profile":15657897354478470176,"path":15485675233132514964,"deps":[[64645024058175247,"pki_types",false,4939645483297780558],[5491919304041016563,"ring",false,10371431482929317026],[8995469080876806959,"untrusted",false,2560156330519654396]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rustls-webpki-0cb88ff33e710804/dep-lib-webpki","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
{"rustc":4758242423518056681,"features":"[\"alloc\", \"default\", \"ring\", \"std\"]","declared_features":"[\"alloc\", \"aws_lc_rs\", \"default\", \"ring\", \"std\"]","target":5054897795206437336,"profile":15657897354478470176,"path":15485675233132514964,"deps":[[64645024058175247,"pki_types",false,4939645483297780558],[5491919304041016563,"ring",false,6363170769880867926],[8995469080876806959,"untrusted",false,2560156330519654396]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rustls-webpki-0cb88ff33e710804/dep-lib-webpki","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View File

@@ -1 +1 @@
e50fe689422d99a8
a4a63ee3380c5986

View File

@@ -1 +1 @@
{"rustc":4758242423518056681,"features":"[\"default\", \"logging\", \"ring\", \"tls12\"]","declared_features":"[\"default\", \"early-data\", \"logging\", \"ring\", \"tls12\"]","target":15569373574890885264,"profile":15657897354478470176,"path":10468347776246938209,"deps":[[64645024058175247,"pki_types",false,4939645483297780558],[7720834239451334583,"tokio",false,621317579489071575],[17020669599254637850,"rustls",false,6856051561458318990]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/tokio-rustls-4624e29860cf9c1f/dep-lib-tokio_rustls","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
{"rustc":4758242423518056681,"features":"[\"default\", \"logging\", \"ring\", \"tls12\"]","declared_features":"[\"default\", \"early-data\", \"logging\", \"ring\", \"tls12\"]","target":15569373574890885264,"profile":15657897354478470176,"path":10468347776246938209,"deps":[[64645024058175247,"pki_types",false,4939645483297780558],[7720834239451334583,"tokio",false,621317579489071575],[17020669599254637850,"rustls",false,11936940970336105319]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/tokio-rustls-4624e29860cf9c1f/dep-lib-tokio_rustls","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/anyhow-7dffd08ca73f1c01/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/anyhow-7dffd08ca73f1c01/out

View File

@@ -1,5 +1,5 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/async-fs-0fc57937ef2ca20d/build_script_build-0fc57937ef2ca20d.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/async-fs-0fc57937ef2ca20d/build_script_build-0fc57937ef2ca20d.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/build.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/build/async-fs-0fc57937ef2ca20d/build_script_build-0fc57937ef2ca20d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/async-fs-0fc57937ef2ca20d/build_script_build-0fc57937ef2ca20d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/build.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/build.rs:

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/async-fs-b8e27f3fae80e0d0/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/async-fs-b8e27f3fae80e0d0/out

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/async-io-7810898f58d6f91d/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/async-io-7810898f58d6f91d/out

View File

@@ -1,5 +1,5 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/async-io-efada4b51bbaa2f4/build_script_build-efada4b51bbaa2f4.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/async-io-efada4b51bbaa2f4/build_script_build-efada4b51bbaa2f4.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/build.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/build/async-io-efada4b51bbaa2f4/build_script_build-efada4b51bbaa2f4: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/async-io-efada4b51bbaa2f4/build_script_build-efada4b51bbaa2f4: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/build.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/build.rs:

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/generic-array-0e994d5e42b52327/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/generic-array-0e994d5e42b52327/out

View File

@@ -1,5 +1,5 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/generic-array-fa0747d719f0609b/build_script_build-fa0747d719f0609b.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/generic-array-0.14.7/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/generic-array-fa0747d719f0609b/build_script_build-fa0747d719f0609b.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/generic-array-0.14.7/build.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/build/generic-array-fa0747d719f0609b/build_script_build-fa0747d719f0609b: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/generic-array-0.14.7/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/generic-array-fa0747d719f0609b/build_script_build-fa0747d719f0609b: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/generic-array-0.14.7/build.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/generic-array-0.14.7/build.rs:

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/io-lifetimes-d63298f07c6f8a87/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/io-lifetimes-d63298f07c6f8a87/out

View File

@@ -1,5 +1,5 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/memoffset-13f5b9470251e33b/build_script_build-13f5b9470251e33b.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/memoffset-0.7.1/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/memoffset-13f5b9470251e33b/build_script_build-13f5b9470251e33b.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/memoffset-0.7.1/build.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/build/memoffset-13f5b9470251e33b/build_script_build-13f5b9470251e33b: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/memoffset-0.7.1/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/memoffset-13f5b9470251e33b/build_script_build-13f5b9470251e33b: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/memoffset-0.7.1/build.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/memoffset-0.7.1/build.rs:

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/memoffset-b58be1b5f143483a/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/memoffset-b58be1b5f143483a/out

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/polling-6e2bdb21b2a0cdd2/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/polling-6e2bdb21b2a0cdd2/out

View File

@@ -1,5 +1,5 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/polling-e1a2d9c2d1e94e5d/build_script_build-e1a2d9c2d1e94e5d.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/polling-2.8.0/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/polling-e1a2d9c2d1e94e5d/build_script_build-e1a2d9c2d1e94e5d.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/polling-2.8.0/build.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/build/polling-e1a2d9c2d1e94e5d/build_script_build-e1a2d9c2d1e94e5d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/polling-2.8.0/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/polling-e1a2d9c2d1e94e5d/build_script_build-e1a2d9c2d1e94e5d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/polling-2.8.0/build.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/polling-2.8.0/build.rs:

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/proc-macro2-1f771d0061c655ef/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/proc-macro2-1f771d0061c655ef/out

View File

@@ -1,5 +1,5 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/ring-3d9fa119325871f2/build_script_build-3d9fa119325871f2.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ring-0.17.14/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/ring-3d9fa119325871f2/build_script_build-3d9fa119325871f2.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ring-0.17.14/build.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/build/ring-3d9fa119325871f2/build_script_build-3d9fa119325871f2: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ring-0.17.14/build.rs
/home/gilles/app/pilot/pilot-v2/target/debug/build/ring-3d9fa119325871f2/build_script_build-3d9fa119325871f2: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ring-0.17.14/build.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ring-0.17.14/build.rs:

View File

@@ -12,7 +12,7 @@ cargo:rerun-if-env-changed=CARGO_CFG_TARGET_OS
cargo:rerun-if-env-changed=CARGO_CFG_TARGET_ENV
cargo:rerun-if-env-changed=CARGO_CFG_TARGET_ENDIAN
OPT_LEVEL = Some(0)
OUT_DIR = Some(/home/gilles/projects/pilot/pilot-v2/target/debug/build/ring-870474cb8250dc62/out)
OUT_DIR = Some(/home/gilles/app/pilot/pilot-v2/target/debug/build/ring-870474cb8250dc62/out)
TARGET = Some(x86_64-unknown-linux-gnu)
CARGO_ENCODED_RUSTFLAGS = Some()
HOST = Some(x86_64-unknown-linux-gnu)
@@ -40,7 +40,7 @@ cargo:rerun-if-env-changed=CFLAGS_x86_64-unknown-linux-gnu
CFLAGS_x86_64-unknown-linux-gnu = None
cargo:rustc-link-lib=static=ring_core_0_17_14_
OPT_LEVEL = Some(0)
OUT_DIR = Some(/home/gilles/projects/pilot/pilot-v2/target/debug/build/ring-870474cb8250dc62/out)
OUT_DIR = Some(/home/gilles/app/pilot/pilot-v2/target/debug/build/ring-870474cb8250dc62/out)
TARGET = Some(x86_64-unknown-linux-gnu)
CARGO_ENCODED_RUSTFLAGS = Some()
HOST = Some(x86_64-unknown-linux-gnu)
@@ -67,92 +67,92 @@ CFLAGS_x86_64_unknown_linux_gnu = None
cargo:rerun-if-env-changed=CFLAGS_x86_64-unknown-linux-gnu
CFLAGS_x86_64-unknown-linux-gnu = None
cargo:rustc-link-lib=static=ring_core_0_17_14__test
cargo:rustc-link-search=native=/home/gilles/projects/pilot/pilot-v2/target/debug/build/ring-870474cb8250dc62/out
cargo:rerun-if-changed=crypto/mem.c
cargo:rerun-if-changed=crypto/curve25519/asm/x25519-asm-arm.S
cargo:rerun-if-changed=crypto/curve25519/internal.h
cargo:rerun-if-changed=crypto/curve25519/curve25519.c
cargo:rerun-if-changed=crypto/curve25519/curve25519_tables.h
cargo:rerun-if-changed=crypto/curve25519/curve25519_64_adx.c
cargo:rerun-if-changed=crypto/constant_time_test.c
cargo:rerun-if-changed=crypto/cpu_intel.c
cargo:rerun-if-changed=crypto/fipsmodule/sha/asm/sha512-armv8.pl
cargo:rerun-if-changed=crypto/fipsmodule/sha/asm/sha256-armv4.pl
cargo:rerun-if-changed=crypto/fipsmodule/sha/asm/sha512-armv4.pl
cargo:rerun-if-changed=crypto/fipsmodule/sha/asm/sha512-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/ec/ecp_nistz384.inl
cargo:rerun-if-changed=crypto/fipsmodule/ec/gfp_p384.c
cargo:rerun-if-changed=crypto/fipsmodule/ec/ecp_nistz.c
cargo:rerun-if-changed=crypto/fipsmodule/ec/ecp_nistz384.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256.c
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256-nistz-table.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256_shared.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256-nistz.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/asm/p256-armv8-asm.pl
cargo:rerun-if-changed=crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl
cargo:rerun-if-changed=crypto/fipsmodule/ec/ecp_nistz.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/util.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256-nistz.c
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256_table.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/gfp_p256.c
cargo:rerun-if-changed=crypto/fipsmodule/aes/aes_nohw.c
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aesni-gcm-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aesv8-gcm-armv8.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/ghashv8-armx.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/ghash-x86.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/ghash-neon-armv8.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/vpaes-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aesni-x86.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/vpaes-armv8.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aesni-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/ghash-armv4.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aesv8-armx.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/ghash-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/vpaes-armv7.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/vpaes-x86.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aes-gcm-avx2-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/bsaes-armv7.pl
cargo:rerun-if-changed=crypto/fipsmodule/bn/montgomery_inv.c
cargo:rerun-if-changed=crypto/fipsmodule/bn/asm/x86_64-mont.pl
cargo:rerun-if-changed=crypto/fipsmodule/bn/asm/x86_64-mont5.pl
cargo:rerun-if-changed=crypto/fipsmodule/bn/asm/armv8-mont.pl
cargo:rerun-if-changed=crypto/fipsmodule/bn/asm/armv4-mont.pl
cargo:rerun-if-changed=crypto/fipsmodule/bn/asm/x86-mont.pl
cargo:rerun-if-changed=crypto/fipsmodule/bn/internal.h
cargo:rerun-if-changed=crypto/fipsmodule/bn/montgomery.c
cargo:rerun-if-changed=crypto/chacha/asm/chacha-x86_64.pl
cargo:rustc-link-search=native=/home/gilles/app/pilot/pilot-v2/target/debug/build/ring-870474cb8250dc62/out
cargo:rerun-if-changed=crypto/chacha/asm/chacha-armv8.pl
cargo:rerun-if-changed=crypto/chacha/asm/chacha-armv4.pl
cargo:rerun-if-changed=crypto/chacha/asm/chacha-x86.pl
cargo:rerun-if-changed=crypto/perlasm/x86gas.pl
cargo:rerun-if-changed=crypto/perlasm/x86_64-xlate.pl
cargo:rerun-if-changed=crypto/chacha/asm/chacha-armv4.pl
cargo:rerun-if-changed=crypto/chacha/asm/chacha-x86_64.pl
cargo:rerun-if-changed=crypto/perlasm/x86nasm.pl
cargo:rerun-if-changed=crypto/perlasm/arm-xlate.pl
cargo:rerun-if-changed=crypto/perlasm/x86asm.pl
cargo:rerun-if-changed=crypto/perlasm/x86nasm.pl
cargo:rerun-if-changed=crypto/perlasm/x86_64-xlate.pl
cargo:rerun-if-changed=crypto/perlasm/x86gas.pl
cargo:rerun-if-changed=crypto/crypto.c
cargo:rerun-if-changed=crypto/poly1305/poly1305_arm.c
cargo:rerun-if-changed=crypto/poly1305/poly1305.c
cargo:rerun-if-changed=crypto/poly1305/poly1305_arm_asm.S
cargo:rerun-if-changed=crypto/mem.c
cargo:rerun-if-changed=crypto/constant_time_test.c
cargo:rerun-if-changed=crypto/internal.h
cargo:rerun-if-changed=crypto/limbs/limbs.c
cargo:rerun-if-changed=crypto/limbs/limbs.inl
cargo:rerun-if-changed=crypto/limbs/limbs.h
cargo:rerun-if-changed=crypto/fipsmodule/bn/montgomery_inv.c
cargo:rerun-if-changed=crypto/fipsmodule/bn/internal.h
cargo:rerun-if-changed=crypto/fipsmodule/bn/montgomery.c
cargo:rerun-if-changed=crypto/fipsmodule/bn/asm/x86_64-mont5.pl
cargo:rerun-if-changed=crypto/fipsmodule/bn/asm/armv4-mont.pl
cargo:rerun-if-changed=crypto/fipsmodule/bn/asm/x86_64-mont.pl
cargo:rerun-if-changed=crypto/fipsmodule/bn/asm/armv8-mont.pl
cargo:rerun-if-changed=crypto/fipsmodule/bn/asm/x86-mont.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/aes_nohw.c
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/ghashv8-armx.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/vpaes-x86.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/ghash-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aesv8-armx.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/ghash-neon-armv8.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/ghash-x86.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aes-gcm-avx2-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aesni-gcm-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aesni-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/ghash-armv4.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aesni-x86.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/vpaes-armv7.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/aesv8-gcm-armv8.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/vpaes-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/bsaes-armv7.pl
cargo:rerun-if-changed=crypto/fipsmodule/aes/asm/vpaes-armv8.pl
cargo:rerun-if-changed=crypto/fipsmodule/sha/asm/sha512-x86_64.pl
cargo:rerun-if-changed=crypto/fipsmodule/sha/asm/sha256-armv4.pl
cargo:rerun-if-changed=crypto/fipsmodule/sha/asm/sha512-armv8.pl
cargo:rerun-if-changed=crypto/fipsmodule/sha/asm/sha512-armv4.pl
cargo:rerun-if-changed=crypto/fipsmodule/ec/gfp_p256.c
cargo:rerun-if-changed=crypto/fipsmodule/ec/gfp_p384.c
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256_table.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/ecp_nistz384.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256-nistz-table.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256-nistz.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/ecp_nistz.c
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256_shared.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256-nistz.c
cargo:rerun-if-changed=crypto/fipsmodule/ec/ecp_nistz384.inl
cargo:rerun-if-changed=crypto/fipsmodule/ec/p256.c
cargo:rerun-if-changed=crypto/fipsmodule/ec/ecp_nistz.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/util.h
cargo:rerun-if-changed=crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl
cargo:rerun-if-changed=crypto/fipsmodule/ec/asm/p256-armv8-asm.pl
cargo:rerun-if-changed=crypto/curve25519/internal.h
cargo:rerun-if-changed=crypto/curve25519/curve25519_tables.h
cargo:rerun-if-changed=crypto/curve25519/curve25519_64_adx.c
cargo:rerun-if-changed=crypto/curve25519/curve25519.c
cargo:rerun-if-changed=crypto/curve25519/asm/x25519-asm-arm.S
cargo:rerun-if-changed=crypto/cipher/asm/chacha20_poly1305_x86_64.pl
cargo:rerun-if-changed=crypto/cipher/asm/chacha20_poly1305_armv8.pl
cargo:rerun-if-changed=crypto/crypto.c
cargo:rerun-if-changed=crypto/limbs/limbs.h
cargo:rerun-if-changed=crypto/limbs/limbs.inl
cargo:rerun-if-changed=crypto/limbs/limbs.c
cargo:rerun-if-changed=crypto/poly1305/poly1305_arm_asm.S
cargo:rerun-if-changed=crypto/poly1305/poly1305.c
cargo:rerun-if-changed=crypto/poly1305/poly1305_arm.c
cargo:rerun-if-changed=include/ring-core/base.h
cargo:rerun-if-changed=include/ring-core/type_check.h
cargo:rerun-if-changed=include/ring-core/mem.h
cargo:rerun-if-changed=include/ring-core/check.h
cargo:rerun-if-changed=crypto/cpu_intel.c
cargo:rerun-if-changed=include/ring-core/target.h
cargo:rerun-if-changed=include/ring-core/check.h
cargo:rerun-if-changed=include/ring-core/type_check.h
cargo:rerun-if-changed=include/ring-core/aes.h
cargo:rerun-if-changed=include/ring-core/base.h
cargo:rerun-if-changed=include/ring-core/mem.h
cargo:rerun-if-changed=include/ring-core/asm_base.h
cargo:rerun-if-changed=third_party/fiat/LICENSE
cargo:rerun-if-changed=third_party/fiat/curve25519_64_msvc.h
cargo:rerun-if-changed=third_party/fiat/curve25519_64.h
cargo:rerun-if-changed=third_party/fiat/p256_64_msvc.h
cargo:rerun-if-changed=third_party/fiat/p256_64.h
cargo:rerun-if-changed=third_party/fiat/p256_32.h
cargo:rerun-if-changed=third_party/fiat/curve25519_32.h
cargo:rerun-if-changed=third_party/fiat/p256_64.h
cargo:rerun-if-changed=third_party/fiat/curve25519_64.h
cargo:rerun-if-changed=third_party/fiat/p256_64_msvc.h
cargo:rerun-if-changed=third_party/fiat/curve25519_64_adx.h
cargo:rerun-if-changed=third_party/fiat/LICENSE
cargo:rerun-if-changed=third_party/fiat/curve25519_64_msvc.h
cargo:rerun-if-changed=third_party/fiat/asm/fiat_curve25519_adx_mul.S
cargo:rerun-if-changed=third_party/fiat/asm/fiat_curve25519_adx_square.S
cargo:rerun-if-changed=third_party/fiat/curve25519_64_adx.h

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/ring-870474cb8250dc62/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/ring-870474cb8250dc62/out

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/rustix-301ffe7856ae3523/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/rustix-301ffe7856ae3523/out

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/rustls-60f7a2d3f2eeb6b2/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/rustls-60f7a2d3f2eeb6b2/out

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/serde-0c79bc1bb5bf9eba/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/serde-0c79bc1bb5bf9eba/out

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/syn-167fb4ec836afd64/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/syn-167fb4ec836afd64/out

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/thiserror-b28f052e3ba5c2bc/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/thiserror-b28f052e3ba5c2bc/out

View File

@@ -1 +1 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/build/zerocopy-5db1835495fc2254/out
/home/gilles/app/pilot/pilot-v2/target/debug/build/zerocopy-5db1835495fc2254/out

View File

@@ -1,8 +1,8 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/aho_corasick-f56eb701d1d265c8.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/ahocorasick.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/automaton.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/dfa.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/contiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/noncontiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/api.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/ext.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/pattern.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/rabinkarp.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/builder.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/generic.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/vector.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/alphabet.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/buffer.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/byte_frequencies.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/debug.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/int.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/prefilter.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/primitives.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/remapper.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/search.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/special.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/aho_corasick-f56eb701d1d265c8.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/ahocorasick.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/automaton.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/dfa.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/contiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/noncontiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/api.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/ext.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/pattern.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/rabinkarp.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/builder.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/generic.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/vector.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/alphabet.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/buffer.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/byte_frequencies.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/debug.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/int.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/prefilter.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/primitives.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/remapper.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/search.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/special.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libaho_corasick-f56eb701d1d265c8.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/ahocorasick.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/automaton.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/dfa.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/contiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/noncontiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/api.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/ext.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/pattern.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/rabinkarp.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/builder.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/generic.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/vector.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/alphabet.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/buffer.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/byte_frequencies.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/debug.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/int.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/prefilter.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/primitives.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/remapper.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/search.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/special.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libaho_corasick-f56eb701d1d265c8.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/ahocorasick.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/automaton.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/dfa.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/contiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/noncontiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/api.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/ext.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/pattern.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/rabinkarp.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/builder.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/generic.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/vector.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/alphabet.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/buffer.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/byte_frequencies.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/debug.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/int.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/prefilter.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/primitives.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/remapper.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/search.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/special.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libaho_corasick-f56eb701d1d265c8.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/ahocorasick.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/automaton.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/dfa.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/contiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/noncontiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/api.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/ext.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/pattern.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/rabinkarp.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/builder.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/generic.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/vector.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/alphabet.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/buffer.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/byte_frequencies.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/debug.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/int.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/prefilter.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/primitives.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/remapper.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/search.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/special.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libaho_corasick-f56eb701d1d265c8.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/ahocorasick.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/automaton.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/dfa.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/contiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/nfa/noncontiguous.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/api.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/ext.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/pattern.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/rabinkarp.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/builder.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/teddy/generic.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/packed/vector.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/mod.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/alphabet.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/buffer.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/byte_frequencies.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/debug.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/int.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/prefilter.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/primitives.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/remapper.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/search.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/util/special.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/lib.rs:
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aho-corasick-1.1.4/src/macros.rs:

View File

@@ -1,8 +1,8 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/anyhow-f83b8d3e8e6fcc5b.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/backtrace.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/chain.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/context.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ensure.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/fmt.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/kind.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ptr.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/wrapper.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/anyhow-f83b8d3e8e6fcc5b.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/backtrace.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/chain.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/context.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ensure.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/fmt.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/kind.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ptr.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/wrapper.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libanyhow-f83b8d3e8e6fcc5b.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/backtrace.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/chain.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/context.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ensure.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/fmt.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/kind.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ptr.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/wrapper.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libanyhow-f83b8d3e8e6fcc5b.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/backtrace.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/chain.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/context.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ensure.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/fmt.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/kind.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ptr.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/wrapper.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libanyhow-f83b8d3e8e6fcc5b.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/backtrace.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/chain.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/context.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ensure.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/fmt.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/kind.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ptr.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/wrapper.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libanyhow-f83b8d3e8e6fcc5b.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/backtrace.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/chain.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/context.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ensure.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/error.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/fmt.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/kind.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/macros.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/ptr.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/wrapper.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/lib.rs:
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.100/src/backtrace.rs:

View File

@@ -1,7 +1,7 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/async_broadcast-96cd5d92478f62ce.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-broadcast-0.5.1/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/async_broadcast-96cd5d92478f62ce.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-broadcast-0.5.1/src/lib.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_broadcast-96cd5d92478f62ce.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-broadcast-0.5.1/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_broadcast-96cd5d92478f62ce.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-broadcast-0.5.1/src/lib.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_broadcast-96cd5d92478f62ce.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-broadcast-0.5.1/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_broadcast-96cd5d92478f62ce.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-broadcast-0.5.1/src/lib.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-broadcast-0.5.1/src/lib.rs:

View File

@@ -1,7 +1,7 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/async_channel-86c3c96ca9a3e4c4.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-channel-2.5.0/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/async_channel-86c3c96ca9a3e4c4.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-channel-2.5.0/src/lib.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_channel-86c3c96ca9a3e4c4.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-channel-2.5.0/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_channel-86c3c96ca9a3e4c4.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-channel-2.5.0/src/lib.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_channel-86c3c96ca9a3e4c4.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-channel-2.5.0/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_channel-86c3c96ca9a3e4c4.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-channel-2.5.0/src/lib.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-channel-2.5.0/src/lib.rs:

View File

@@ -1,7 +1,7 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/async_executor-f15b8cf4d2656fdf.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.3/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/async_executor-f15b8cf4d2656fdf.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.3/src/lib.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_executor-f15b8cf4d2656fdf.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.3/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_executor-f15b8cf4d2656fdf.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.3/src/lib.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_executor-f15b8cf4d2656fdf.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.3/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_executor-f15b8cf4d2656fdf.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.3/src/lib.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.3/src/lib.rs:

View File

@@ -1,7 +1,7 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/async_fs-3ea0bd7facca6ae4.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/async_fs-3ea0bd7facca6ae4.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/src/lib.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_fs-3ea0bd7facca6ae4.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_fs-3ea0bd7facca6ae4.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/src/lib.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_fs-3ea0bd7facca6ae4.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/src/lib.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_fs-3ea0bd7facca6ae4.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/src/lib.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-fs-1.6.0/src/lib.rs:

View File

@@ -1,8 +1,8 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/async_io-c29a4c449a4f1a74.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/driver.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/reactor.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/async_io-c29a4c449a4f1a74.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/driver.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/reactor.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_io-c29a4c449a4f1a74.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/driver.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/reactor.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_io-c29a4c449a4f1a74.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/driver.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/reactor.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_io-c29a4c449a4f1a74.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/driver.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/reactor.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_io-c29a4c449a4f1a74.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/driver.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/reactor.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/lib.rs:
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-1.13.0/src/driver.rs:

View File

@@ -1,8 +1,8 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/async_lock-4d7615b53c137535.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/barrier.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/mutex.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/once_cell.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/futures.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/raw.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/semaphore.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/async_lock-4d7615b53c137535.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/barrier.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/mutex.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/once_cell.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/futures.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/raw.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/semaphore.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_lock-4d7615b53c137535.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/barrier.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/mutex.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/once_cell.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/futures.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/raw.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/semaphore.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_lock-4d7615b53c137535.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/barrier.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/mutex.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/once_cell.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/futures.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/raw.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/semaphore.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_lock-4d7615b53c137535.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/barrier.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/mutex.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/once_cell.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/futures.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/raw.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/semaphore.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_lock-4d7615b53c137535.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/barrier.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/mutex.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/once_cell.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/futures.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/rwlock/raw.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/semaphore.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/lib.rs:
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-lock-2.8.0/src/barrier.rs:

View File

@@ -1,6 +1,6 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/async_recursion-b57f76abda3b94ad.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/expand.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/parse.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/async_recursion-b57f76abda3b94ad.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/expand.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/parse.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_recursion-b57f76abda3b94ad.so: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/expand.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/parse.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_recursion-b57f76abda3b94ad.so: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/expand.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/parse.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/lib.rs:
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-recursion-1.1.1/src/expand.rs:

View File

@@ -1,6 +1,6 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/async_trait-e7e842722d3c8921.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/args.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/bound.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/expand.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/lifetime.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/parse.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/receiver.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/verbatim.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/async_trait-e7e842722d3c8921.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/args.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/bound.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/expand.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/lifetime.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/parse.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/receiver.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/verbatim.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libasync_trait-e7e842722d3c8921.so: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/args.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/bound.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/expand.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/lifetime.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/parse.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/receiver.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/verbatim.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libasync_trait-e7e842722d3c8921.so: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/args.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/bound.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/expand.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/lifetime.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/parse.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/receiver.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/verbatim.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/lib.rs:
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-trait-0.1.89/src/args.rs:

View File

@@ -1,8 +1,8 @@
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/block_buffer-a74feccb0c0c61e4.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/sealed.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/block_buffer-a74feccb0c0c61e4.d: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/sealed.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libblock_buffer-a74feccb0c0c61e4.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/sealed.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libblock_buffer-a74feccb0c0c61e4.rlib: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/sealed.rs
/home/gilles/projects/pilot/pilot-v2/target/debug/deps/libblock_buffer-a74feccb0c0c61e4.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/sealed.rs
/home/gilles/app/pilot/pilot-v2/target/debug/deps/libblock_buffer-a74feccb0c0c61e4.rmeta: /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/lib.rs /home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/sealed.rs
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/lib.rs:
/home/gilles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/block-buffer-0.10.4/src/sealed.rs:

Some files were not shown because too many files have changed in this diff Show More