diff --git a/.$dashboard.drawio.bkp b/.$dashboard.drawio.bkp
new file mode 100644
index 0000000..4ca7d62
--- /dev/null
+++ b/.$dashboard.drawio.bkp
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Capture d’écran du 2025-11-23 14-41-36.png b/Capture d’écran du 2025-11-23 14-41-36.png
new file mode 100644
index 0000000..28491e0
Binary files /dev/null and b/Capture d’écran du 2025-11-23 14-41-36.png differ
diff --git a/Capture d’écran du 2025-11-23 14-41-53.png b/Capture d’écran du 2025-11-23 14-41-53.png
new file mode 100644
index 0000000..89d6370
Binary files /dev/null and b/Capture d’écran du 2025-11-23 14-41-53.png differ
diff --git a/Capture d’écran du 2025-11-23 14-42-03.png b/Capture d’écran du 2025-11-23 14-42-03.png
new file mode 100644
index 0000000..95ccd69
Binary files /dev/null and b/Capture d’écran du 2025-11-23 14-42-03.png differ
diff --git a/README.md b/README.md
index cf696fe..a190b3c 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,236 @@
-# ssh-web-launcher
+# SSH Launcher
+Lanceur SSH et services via une extension GNOME Shell et une application GTK.
+Permet d'accéder rapidement à vos machines depuis la barre GNOME.
+
+## Fonctionnalités
+
+- Icône dans la barre supérieure GNOME
+- Popup avec liste des équipements et services
+- Configuration via fichier YAML
+- Support SSH, HTTP, HTTPS, VNC...
+- Page HTML standalone alternative
+
+## Architecture
+
+```
+┌─────────────────────────────────────────────────────────┐
+│ GNOME Shell │
+│ ┌─────────────────────────────────────────────────┐ │
+│ │ Extension (icône barre) ──► App GTK + WebKit │ │
+│ └─────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ config/equipements.yaml │
+│ │ │
+│ ▼ │
+│ Interface HTML dynamique │
+│ │ │
+│ ▼ │
+│ Clic ──► Handler système ──► Terminal/App │
+└─────────────────────────────────────────────────────────┘
+```
+
+## Structure du projet
+
+```
+ssh-web-launcher/
+├── config/
+│ └── equipements.yaml # Configuration des machines
+├── extension/
+│ ├── metadata.json # Métadonnées extension GNOME
+│ ├── extension.js # Code de l'extension
+│ └── stylesheet.css # Style de l'icône
+├── app/
+│ ├── ssh-launcher-gtk.py # Application GTK4 + WebKit
+│ └── ssh-launcher-gtk.desktop
+├── scripts/
+│ ├── ssh-url # Handler SSH
+│ └── install.sh # Script d'installation
+├── desktop/
+│ └── ssh-url.desktop # Handler SSH .desktop
+├── index.html # Page standalone
+└── README.md
+```
+
+## Prérequis
+
+- GNOME Shell 42+
+- Python 3
+- GTK3 et WebKit2 4.1
+
+### Installation des dépendances (Debian/Ubuntu)
+
+```bash
+sudo apt install python3 python3-gi python3-yaml \
+ gir1.2-gtk-3.0 gir1.2-webkit2-4.1
+```
+
+## Installation rapide
+
+```bash
+./scripts/install.sh
+```
+
+Le script :
+1. Vérifie les dépendances
+2. Configure les chemins
+3. Installe le handler SSH
+4. Installe l'extension GNOME Shell
+
+### Activer l'extension
+
+Après installation, redémarrez GNOME Shell (Alt+F2 → `r` → Entrée) puis :
+
+```bash
+gnome-extensions enable ssh-launcher@local
+```
+
+Ou utilisez l'application "Extensions" / "Extension Manager".
+
+## Installation manuelle
+
+### 1. Rendre les scripts exécutables
+
+```bash
+chmod +x scripts/ssh-url
+chmod +x scripts/install.sh
+chmod +x app/ssh-launcher-gtk.py
+```
+
+### 2. Installer le handler SSH
+
+```bash
+cp desktop/ssh-url.desktop ~/.local/share/applications/
+xdg-mime default ssh-url.desktop x-scheme-handler/ssh
+update-desktop-database ~/.local/share/applications/
+```
+
+### 3. Installer l'extension GNOME
+
+```bash
+mkdir -p ~/.local/share/gnome-shell/extensions/ssh-launcher@local
+cp extension/* ~/.local/share/gnome-shell/extensions/ssh-launcher@local/
+```
+
+Redémarrez GNOME Shell et activez l'extension.
+
+## Configuration
+
+Éditez `config/equipements.yaml` pour ajouter vos machines :
+
+```yaml
+equipements:
+ - nom: "Mon Serveur"
+ ip: "192.168.1.100"
+ categorie: "Serveurs"
+ services:
+ - nom: "SSH"
+ icon: "utilities-terminal"
+ url: "ssh://admin@192.168.1.100"
+ - nom: "Web"
+ icon: "web-browser"
+ url: "https://192.168.1.100"
+```
+
+### Icônes disponibles
+
+| Icône | Nom |
+|-------|-----|
+| 💻 | `utilities-terminal` |
+| 🌐 | `web-browser` |
+| ⚙️ | `applications-system` |
+| 🖥️ | `preferences-desktop-remote-desktop` |
+| 🔒 | `security-high` |
+| 🏠 | `go-home` |
+| 🖧 | `network-server` |
+
+## Test
+
+### Tester l'application GTK directement
+
+```bash
+python3 app/ssh-launcher-gtk.py
+```
+
+### Tester le handler SSH
+
+```bash
+./scripts/ssh-url "ssh://user@localhost"
+```
+
+### Tester la page standalone
+
+```bash
+firefox index.html
+```
+
+## Dépannage
+
+### L'extension n'apparaît pas
+
+1. Vérifiez que GNOME Shell est redémarré
+2. Vérifiez l'activation :
+ ```bash
+ gnome-extensions list
+ gnome-extensions enable ssh-launcher@local
+ ```
+
+### Erreur WebKit / écran noir
+
+Si vous avez des erreurs GPU (GBM, DRM), l'application désactive automatiquement
+l'accélération matérielle. Si le problème persiste, lancez avec :
+```bash
+WEBKIT_DISABLE_COMPOSITING_MODE=1 python3 app/ssh-launcher-gtk.py
+```
+
+### Le handler SSH ne fonctionne pas
+
+```bash
+xdg-mime query default x-scheme-handler/ssh
+# Doit retourner : ssh-url.desktop
+```
+
+### Logs de l'extension
+
+```bash
+journalctl -f -o cat /usr/bin/gnome-shell
+```
+
+## Désinstallation
+
+```bash
+# Extension GNOME
+rm -rf ~/.local/share/gnome-shell/extensions/ssh-launcher@local
+
+# Applications
+rm ~/.local/share/applications/ssh-url.desktop
+rm ~/.local/share/applications/ssh-launcher-gtk.desktop
+update-desktop-database ~/.local/share/applications/
+```
+
+## Utilisation standalone (sans extension)
+
+La page `index.html` peut être utilisée indépendamment :
+
+```bash
+firefox index.html
+```
+
+Elle nécessite uniquement le handler SSH (`ssh-url.desktop`).
+
+## Mini-apps
+
+Le dossier `app/tools/` contient des petits outils autonomes comme `color-picker` : lancez-les via
+
+```bash
+python3 app/tools/color-picker/color_picker.py
+```
+
+Le script `run_color_picker.py` sert uniquement pour les essais côté navigateur (il démarre un mini-serveur local).
+
+Le dashboard peut aussi démarrer ces scripts via la section `minitools` (voir `config/equipements.yaml`).
+
+## Licence
+
+Libre d'utilisation et de modification.
diff --git a/app/__pycache__/ssh-launcher-gtk.cpython-313.pyc b/app/__pycache__/ssh-launcher-gtk.cpython-313.pyc
new file mode 100644
index 0000000..8ca88d4
Binary files /dev/null and b/app/__pycache__/ssh-launcher-gtk.cpython-313.pyc differ
diff --git a/app/ssh-launcher-gtk.desktop b/app/ssh-launcher-gtk.desktop
new file mode 100644
index 0000000..26ed753
--- /dev/null
+++ b/app/ssh-launcher-gtk.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Name=SSH Launcher
+Comment=Interface de connexion SSH et services
+Exec=python3 /home/gilles/Documents/vscode/ssh-web-launcher/app/ssh-launcher-gtk.py
+Icon=utilities-terminal
+Type=Application
+Terminal=false
+Categories=Network;Utility;
+Keywords=ssh;terminal;remote;connexion;
diff --git a/app/ssh-launcher-gtk.py b/app/ssh-launcher-gtk.py
new file mode 100755
index 0000000..4753035
--- /dev/null
+++ b/app/ssh-launcher-gtk.py
@@ -0,0 +1,764 @@
+#!/usr/bin/env python3
+"""
+Dashboard Launcher - Application GTK3 + WebKit2
+Affiche une interface web pour lancer des connexions SSH et autres services.
+"""
+
+import os
+import sys
+import yaml
+import gi
+import subprocess
+import re
+import threading
+
+gi.require_version('Gtk', '3.0')
+gi.require_version('WebKit2', '4.1')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk, WebKit2, Gio, Gdk, GLib
+import cairo
+
+PID_FILE = '/tmp/dashboard-launcher.pid'
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+PROJECT_DIR = os.path.dirname(SCRIPT_DIR)
+CONFIG_FILE = os.path.join(PROJECT_DIR, 'config', 'equipements.yaml')
+ICONS_DIR = os.path.join(PROJECT_DIR, 'icons')
+
+# Cache pour les résultats de ping
+ping_results = {}
+
+
+def ping_host(ip, timeout=1):
+ """Ping une IP et retourne True si accessible."""
+ try:
+ # Nettoyer l'IP (enlever les doubles points, etc.)
+ clean_ip = ip.replace('..', '.')
+ result = subprocess.run(
+ ['ping', '-c', '1', '-W', str(timeout), clean_ip],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ timeout=timeout + 1
+ )
+ return result.returncode == 0
+ except Exception:
+ return False
+
+
+def ping_all_hosts(hosts, callback):
+ """Ping toutes les IPs en parallèle et appelle callback avec les résultats."""
+ def ping_thread(ip):
+ result = ping_host(ip)
+ ping_results[ip] = result
+
+ threads = []
+ for ip in hosts:
+ t = threading.Thread(target=ping_thread, args=(ip,))
+ t.daemon = True
+ t.start()
+ threads.append(t)
+
+ def wait_and_callback():
+ for t in threads:
+ t.join(timeout=2)
+ GLib.idle_add(callback)
+
+ threading.Thread(target=wait_and_callback, daemon=True).start()
+
+
+def load_config():
+ """Charge la configuration depuis le fichier YAML."""
+ try:
+ with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
+ return yaml.safe_load(f) or {}
+ except (FileNotFoundError, yaml.YAMLError) as e:
+ print(f"Erreur config: {e}")
+ return {}
+
+
+def get_icon_html(icon_name, size=32):
+ """Retourne le HTML pour une icône."""
+ if not icon_name:
+ return f'🔗'
+
+ # Chemin vers image dans icons/
+ if icon_name.startswith('icons/'):
+ icon_path = os.path.join(PROJECT_DIR, icon_name)
+ if os.path.exists(icon_path):
+ return f'
'
+
+ # Emojis de fallback
+ icons = {
+ 'utilities-terminal': '💻', 'terminal': '💻', 'web-browser': '🌐',
+ 'applications-system': '⚙️', 'server': '🖥️', 'network-server': '🖧',
+ 'preferences-desktop-remote-desktop': '🖥️', 'security-high': '🔒',
+ 'go-home': '🏠', 'system-file-manager': '📁', 'firefox': '🦊',
+ 'google-chrome': '🌐', 'code': '📝',
+ }
+ emoji = icons.get(icon_name, icon_name if len(icon_name) <= 2 else '🔗')
+ return f'{emoji}'
+
+
+def generate_html(config):
+ """Génère le HTML avec layout: local en haut, distant à gauche, url à droite."""
+
+ apparence = config.get('apparence', {})
+ fenetre = config.get('fenetre', {})
+ theme = apparence.get('theme', 'dark')
+ font_size = apparence.get('police_taille', 14)
+ icon_distant = apparence.get('icon_taille_distant', 48)
+ icon_local = apparence.get('icon_taille_local', 64)
+ icon_url = apparence.get('icon_taille_url', 64)
+ icon_minitools = apparence.get('icon_taille_minitools', 32)
+ show_local_labels = apparence.get('afficher_label_local', True)
+ border_radius = apparence.get('border_radius', 12)
+ espacement_local = apparence.get('espacement_local', 10)
+ espacement_distant = apparence.get('espacement_distant', 6)
+ espacement_url = apparence.get('espacement_url', 8)
+ espacement_minitools = apparence.get('espacement_minitools', 8)
+ icon_fermer = apparence.get('icon_taille_fermer', 22)
+ icon_parametre = apparence.get('icon_taille_parametre', 22)
+ icon_theme_width = apparence.get('icon_taille_theme', 60)
+ icon_theme_height = int(icon_theme_width * 238 / 512) # Ratio de l'image night-day.png
+ border_radius_local = apparence.get('border_radius_local', 12)
+
+ # Colonnes configurables
+ col_url = fenetre.get('section_url', {}).get('colonne', 2)
+
+ distant = config.get('distant', [])
+ local = config.get('local', [])
+ urls = config.get('url', [])
+
+ # Section DISTANT avec status ping
+ distant_html = ""
+ for machine in distant:
+ ip = machine.get('ip', '')
+ nom = machine.get('nom', '')
+ # Vérifier le statut ping (vert si up, rouge si down, gris si inconnu)
+ is_up = ping_results.get(ip)
+ if is_up is None:
+ status_class = "status-unknown"
+ elif is_up:
+ status_class = "status-up"
+ else:
+ status_class = "status-down"
+
+ services_html = ""
+ for service in machine.get('services', []):
+ icon_html = get_icon_html(service.get('icon', ''), icon_distant)
+ services_html += f'''
+
+ {icon_html}
+ {service.get('nom', '')}
+ '''
+ distant_html += f'''
+
+
+
{ip}
+
{services_html}
+
'''
+
+ # Section LOCAL
+ local_html = ""
+ for app in local:
+ icon_html = get_icon_html(app.get('icon', ''), icon_local)
+ label_html = f'{app.get("nom", "")}' if show_local_labels else ''
+ local_html += f'''
+
+ {icon_html}
+ {label_html}
+ '''
+
+ minitools = config.get('minitools', [])
+ minitools_html = ""
+ for tool in minitools:
+ icon_html = get_icon_html(tool.get('icon', ''), icon_minitools)
+ label_html = f'{tool.get("nom", "")}' if show_local_labels else ''
+ href = ''
+ if tool.get('command'):
+ href = f"app://run/{tool.get('command')}"
+ elif tool.get('url'):
+ href = tool.get('url')
+ if not href:
+ continue
+ minitools_html += f'''
+
+ {icon_html}
+ {label_html}
+ '''
+
+ # Section URL
+ url_html = ""
+ for bookmark in urls:
+ icon_html = get_icon_html(bookmark.get('icon', ''), icon_url)
+ url_html += f'''
+
+ {icon_html}
+ {bookmark.get('nom', '')}
+ '''
+
+ # Couleurs thème Adwaita - box = fond équipement (clair), item = fond service (foncé)
+ if theme == 'light':
+ c = {'bg': '#fafafa', 'container': '#ffffff', 'header': '#ebebeb', 'border': '#d0d0d0',
+ 'text': '#2e2e2e', 'text2': '#5e5e5e', 'box': '#e8e8e8', 'item': '#f5f5f5',
+ 'hover': '#888b8f', 'status': '#2ec27e'}
+ else:
+ c = {'bg': '#1e1e1e', 'container': '#242424', 'header': '#303030', 'border': '#1a1a1a',
+ 'text': '#ffffff', 'text2': '#999999', 'box': '#3d3d3d', 'item': '#2d2d2d',
+ 'hover': '#888b8f', 'status': '#2ec27e'}
+
+ # Surcharge avec les couleurs personnalisées si définies
+ if apparence.get('couleur_fond'):
+ c['container'] = apparence['couleur_fond']
+ if apparence.get('couleur_header'):
+ c['header'] = apparence['couleur_header']
+ else:
+ c['header'] = c['container'] # Par défaut, même couleur que le fond
+ if apparence.get('couleur_item'):
+ c['item'] = apparence['couleur_item']
+ if apparence.get('couleur_bordure'):
+ c['border'] = apparence['couleur_bordure']
+ if apparence.get('couleur_hover'):
+ c['hover'] = apparence['couleur_hover']
+
+ # Couleurs séparées pour chaque section (local, distant, url)
+ c['box_local'] = apparence.get('couleur_box_local') or c['box']
+ c['box_distant'] = apparence.get('couleur_box_distant') or c['box']
+ c['box_url'] = apparence.get('couleur_box_url') or c['box']
+ c['box_minitools'] = apparence.get('couleur_box_minitools') or c['box_local']
+
+ # Image night-day: 512x238 (ratio 2.15:1) - affichée entièrement comme toggle
+
+ html = f'''
+
+
+
+
+
+
+
+
+
{distant_html}
+
{url_html}
+
+
{minitools_html}
+
+'''
+ return html
+
+
+class DashboardLauncherWindow(Gtk.Window):
+ def __init__(self):
+ super().__init__(title="Dashboard Launcher")
+ self.config = load_config()
+ self.fenetre_config = self.config.get('fenetre', {})
+
+ # Activer la transparence pour éviter le flash blanc
+ screen = self.get_screen()
+ visual = screen.get_rgba_visual()
+ if visual:
+ self.set_visual(visual)
+ self.set_app_paintable(True)
+ self.connect('draw', self.on_draw)
+
+ self.set_type_hint(Gdk.WindowTypeHint.POPUP_MENU)
+ self.set_skip_taskbar_hint(True)
+ self.set_skip_pager_hint(True)
+ self.set_decorated(False) # Pas de décoration pour éviter le flash
+ self.set_resizable(False)
+
+ if self.fenetre_config.get('toujours_au_dessus', False):
+ self.set_keep_above(True)
+
+ largeur = self.fenetre_config.get('largeur', 800)
+ hauteur = self.fenetre_config.get('hauteur', 450)
+ self.set_default_size(largeur, hauteur)
+
+ display = Gdk.Display.get_default()
+ ecran_num = self.fenetre_config.get('ecran', -1)
+
+ if ecran_num is None or ecran_num < 0:
+ seat = display.get_default_seat()
+ pointer = seat.get_pointer()
+ _, mouse_x, mouse_y = pointer.get_position()
+ monitor = display.get_monitor_at_point(mouse_x, mouse_y)
+ else:
+ n_monitors = display.get_n_monitors()
+ monitor = display.get_monitor(ecran_num) if ecran_num < n_monitors else display.get_primary_monitor() or display.get_monitor(0)
+
+ geom = monitor.get_geometry()
+ centrer = self.fenetre_config.get('centrer', True)
+ x = self.fenetre_config.get('x', 0)
+ y = self.fenetre_config.get('y', 50)
+
+ if centrer:
+ self.move(geom.x + (geom.width - largeur) // 2, geom.y + y)
+ else:
+ self.move(geom.x + x, geom.y + y)
+
+ self.connect('key-press-event', self.on_key_press)
+
+ settings = WebKit2.Settings()
+ settings.set_property('hardware-acceleration-policy', WebKit2.HardwareAccelerationPolicy.NEVER)
+
+ self.webview = WebKit2.WebView()
+ self.webview.set_settings(settings)
+ self.webview.connect('decide-policy', self.on_decide_policy)
+
+ # Fond transparent pour éviter le flash blanc
+ bg_color = Gdk.RGBA()
+ bg_color.red = 0
+ bg_color.green = 0
+ bg_color.blue = 0
+ bg_color.alpha = 0 # Transparent
+ self.webview.set_background_color(bg_color)
+
+ self.reload_content()
+ self.add(self.webview)
+ self.connect('destroy', self.on_destroy)
+
+ # Lancer le ping en arrière-plan
+ self.ping_timer_id = None
+ self.autohide_timer_id = None
+ self.mouse_inside = True
+ self.start_ping_check()
+
+ # Configurer le ping périodique
+ ping_interval = self.config.get('apparence', {}).get('ping_intervalle', 360)
+ if ping_interval > 0:
+ self.ping_timer_id = GLib.timeout_add_seconds(ping_interval, self.on_ping_timer)
+
+ # Fermeture sur perte de focus (clic extérieur)
+ if self.fenetre_config.get('fermer_sur_clic_exterieur', True):
+ self.connect('focus-out-event', self.on_focus_out)
+
+ # Autohide: fermer si souris hors fenêtre pendant X secondes
+ autohide_delay = self.fenetre_config.get('autohide', 0)
+ if autohide_delay > 0:
+ self.autohide_delay = autohide_delay
+ self.connect('enter-notify-event', self.on_mouse_enter)
+ self.connect('leave-notify-event', self.on_mouse_leave)
+
+ def on_draw(self, widget, cr):
+ """Dessine un fond transparent."""
+ cr.set_source_rgba(0, 0, 0, 0)
+ cr.set_operator(cairo.OPERATOR_SOURCE)
+ cr.paint()
+ return False
+
+ def reload_content(self):
+ self.config = load_config()
+ self.webview.load_html(generate_html(self.config), 'file://')
+
+ def start_ping_check(self):
+ """Lance le ping de toutes les IPs en arrière-plan."""
+ distant = self.config.get('distant', [])
+ hosts = [m.get('ip', '') for m in distant if m.get('ip')]
+ ping_all_hosts(hosts, self.on_ping_complete)
+
+ def on_ping_complete(self):
+ """Callback appelé quand tous les pings sont terminés."""
+ self.reload_content()
+
+ def on_ping_timer(self):
+ """Timer pour le ping périodique."""
+ self.start_ping_check()
+ return True # Continuer le timer
+
+ def on_destroy(self, widget):
+ """Nettoyage à la fermeture."""
+ if self.ping_timer_id:
+ GLib.source_remove(self.ping_timer_id)
+ if self.autohide_timer_id:
+ GLib.source_remove(self.autohide_timer_id)
+ Gtk.main_quit()
+
+ def on_focus_out(self, widget, event):
+ """Ferme la fenêtre quand elle perd le focus."""
+ self.destroy()
+ return True
+
+ def on_mouse_enter(self, widget, event):
+ """Souris entre dans la fenêtre - annuler le timer autohide."""
+ self.mouse_inside = True
+ if self.autohide_timer_id:
+ GLib.source_remove(self.autohide_timer_id)
+ self.autohide_timer_id = None
+ return False
+
+ def on_mouse_leave(self, widget, event):
+ """Souris quitte la fenêtre - démarrer le timer autohide."""
+ self.mouse_inside = False
+ if self.autohide_timer_id:
+ GLib.source_remove(self.autohide_timer_id)
+ self.autohide_timer_id = GLib.timeout_add_seconds(
+ self.autohide_delay, self.on_autohide_timeout
+ )
+ return False
+
+ def on_autohide_timeout(self):
+ """Timer autohide expiré - fermer si souris toujours hors fenêtre."""
+ if not self.mouse_inside:
+ self.destroy()
+ return False # Ne pas répéter
+
+ def toggle_theme(self):
+ current = self.config.get('apparence', {}).get('theme', 'dark')
+ new_theme = 'light' if current == 'dark' else 'dark'
+ try:
+ with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
+ content = f.read()
+ # Regex précise: uniquement "theme:" en début de ligne (après espaces)
+ content = re.sub(r'^(\s*)theme:\s*"?\w+"?', rf'\1theme: "{new_theme}"', content, flags=re.MULTILINE)
+ with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
+ f.write(content)
+ self.reload_content()
+ except Exception as e:
+ print(f"Erreur thème: {e}")
+
+ def on_key_press(self, widget, event):
+ if event.keyval == Gdk.KEY_Escape:
+ self.destroy()
+ return True
+ return False
+
+ def on_decide_policy(self, webview, decision, decision_type):
+ if decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION:
+ uri = decision.get_navigation_action().get_request().get_uri()
+
+ if uri == 'app://close':
+ self.destroy()
+ elif uri == 'app://toggle-theme':
+ self.toggle_theme()
+ elif uri == 'app://settings':
+ subprocess.Popen(['xdg-open', CONFIG_FILE])
+ elif uri and uri.startswith('app://run/'):
+ subprocess.Popen(uri.replace('app://run/', '').split())
+ elif uri and (uri.startswith('smb://') or uri.startswith('nfs://')):
+ # SMB/NFS: monter avec gio et ouvrir dans Nautilus
+ self.mount_and_open(uri)
+ elif uri and not uri.startswith('file://') and not uri.startswith('about:'):
+ try:
+ Gio.AppInfo.launch_default_for_uri(uri, None)
+ except Exception as e:
+ print(f"Erreur URI: {e}")
+ else:
+ return False
+ decision.ignore()
+ return True
+ return False
+
+ def mount_and_open(self, uri):
+ """Monte un partage SMB/NFS avec gio et l'ouvre dans Nautilus."""
+ def open_nautilus():
+ subprocess.Popen(['nautilus', uri])
+ return False # Ne pas répéter
+
+ def mount_thread():
+ try:
+ # Monter le partage avec gio mount
+ subprocess.run(
+ ['gio', 'mount', uri],
+ capture_output=True,
+ text=True,
+ timeout=30
+ )
+ except subprocess.TimeoutExpired:
+ print(f"Timeout montage: {uri}")
+ except Exception as e:
+ print(f"Erreur montage {uri}: {e}")
+ finally:
+ # Ouvrir dans Nautilus une seule fois
+ GLib.idle_add(open_nautilus)
+
+ # Lancer le montage en arrière-plan pour ne pas bloquer l'UI
+ threading.Thread(target=mount_thread, daemon=True).start()
+
+
+def is_running():
+ if os.path.exists(PID_FILE):
+ try:
+ with open(PID_FILE, 'r') as f:
+ pid = int(f.read().strip())
+ os.kill(pid, 0)
+ return pid
+ except (OSError, ValueError):
+ pass
+ return None
+
+
+def main():
+ if '--toggle' in sys.argv and is_running():
+ import signal
+ os.kill(is_running(), signal.SIGTERM)
+ if os.path.exists(PID_FILE):
+ os.remove(PID_FILE)
+ sys.exit(0)
+
+ if is_running():
+ print(f"Dashboard déjà en cours")
+ sys.exit(0)
+
+ os.environ['WEBKIT_DISABLE_COMPOSITING_MODE'] = '1'
+
+ with open(PID_FILE, 'w') as f:
+ f.write(str(os.getpid()))
+
+ import atexit
+ atexit.register(lambda: os.path.exists(PID_FILE) and os.remove(PID_FILE))
+
+ win = DashboardLauncherWindow()
+ win.show_all()
+ Gtk.main()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/app/tools/color-picker/DEVELOPMENT_PROMPT.md b/app/tools/color-picker/DEVELOPMENT_PROMPT.md
new file mode 100644
index 0000000..311a982
--- /dev/null
+++ b/app/tools/color-picker/DEVELOPMENT_PROMPT.md
@@ -0,0 +1,45 @@
+# Prompt de développement – Color Picker mini-app
+
+## Analyse du besoin
+- Interface compacte avec roue chromatique, zone palette, volet latéral repliable "Theme CSS" affichant styles/typo.
+- Actions attendues : sélectionner une couleur, afficher sa complémentaire, copier les codes HEX/RGB, afficher aperçu (pipette), streamer palettes CSS/typos stockées.
+- Stockage : dossiers `themes/` et `palettes/` pour y déposer JSON/CSS réutilisables.
+- Intégration : doit pouvoir être lancé depuis la barre mini-tools du dashboard et cohabiter avec d'autres mini-apps.
+
+## Proposition d'architecture
+1. Frontend web léger (HTML/CSS/JS) rendu via Tauri/Electron ou WebView GTK, pour un look moderne.
+2. Backend minimal (Node.js ou Python) servant les fichiers statiques et exposant une API (ex. `GET /themes`, `GET /palettes`).
+3. Composants clés :
+ - Colonne droite : palette avec carte couleur + bouton copier + couleur complémentaire.
+ - Centre : roue chromatique interactive + pipette et aperçu couleur sélectionnée.
+ - Volet gauche repliable : aperçu "Theme CSS" (fond, typographies, boutons) chargé depuis `themes/`.
+ - Barre inférieure : actions rapides (copier, exporter en CSS, ouvrir palette).
+4. Données :
+ - `palettes/*.json` (nom, description, code couleur, tags, langage associé).
+ - `themes/*.css` (aperçu d'un thème complet plus métadonnées).
+
+## Choix techniques
+- **Langage** : JavaScript/TypeScript + Svelte ou Vue pour agile; empaquetage via Tauri pour faible empreinte. Alternativement, Python + GTK si l’environnement cible préfère GTK.
+- **UI** : `chroma.js` ou `tinycolor` pour la palette/roue et le calcul des complémentaires, Web APIs pour la pipette (canvas + input color).
+- **Données** : JSON/YAML stockés dans `app/tools/color-picker/` et chargés dynamiquement côté front.
+- **Lancement** : un script `scripts/color-picker.sh` ou un mini-wrapper Python qui ouvre la WebView / Tauri.
+
+## Plan
+1. Créer la structure `app/tools/color-picker/{src, themes, palettes, assets}`.
+2. Initialiser un petit serveur `main.ts` ou `main.py` (selon stack) pour servir l’interface et exposer les APIs.
+3. Développer l’UI (roue, volet thèmes, palettes, pipette) avec interactions décrites.
+4. Charger dynamiquement `palettes/*.json` et `themes/*.css`, proposer un panneau pour ajouter/modifier.
+5. Ajouter boutons “copier” qui placent HEX/RGB dans le presse-papier.
+6. Documenter le lancement et l’intégration dans `config/equipements.yaml` via mini-tool.
+
+## Tests
+- Test manuel : lancer l’app (via Tauri ou un script) et valider roue réactive, volet dépliable, copie de couleur.
+- Tests unitaires (JS) sur le parseur de palettes et les calculs de couleurs (complémentaire, contraste).
+
+## TODO
+1. Définir format palette/metadata.
+2. Créer jeux de palettes (Monokai, Solarized, thèmes web populaires).
+3. Implémenter pipette + copie automatique des codes.
+4. Ajouter vue “Theme CSS” avec CSS/Font preview.
+5. Préparer commande de lancement utilisable dans `minitools`.
+6. Documenter la mini-app dans `README`.
diff --git a/app/tools/color-picker/README.md b/app/tools/color-picker/README.md
new file mode 100644
index 0000000..9ed3d22
--- /dev/null
+++ b/app/tools/color-picker/README.md
@@ -0,0 +1,22 @@
+# Color Picker Mini-App
+
+Cette mini-application fournit une roue chromatique, des palettes prédéfinies, un volet "Theme CSS" et la capacité de copier des couleurs (HEX / complémentaire).
+
+## Structure
+- `run_color_picker.py` : lance un serveur HTTP local (`127.0.0.1:9005`) et ouvre le navigateur sur `src/index.html`.
+- `src/` : interface brutes (`index.html`, `style.css`, `app.js`).
+- `palettes/` : fichiers JSON décrivant les palettes disponibles.
+- `themes/` : thèmes CSS et manifeste (`themes.json`).
+- `assets/` : placeholders pour icônes, images, etc.
+
+## Développement
+1. Mettre à jour `palettes/*.json` pour ajouter de nouvelles palettes ou variantes.
+2. Ajouter un thème CSS dans `themes/` puis l’enregistrer dans `themes/themes.json` pour qu’il apparaisse dans la liste.
+3. Modifier `src/app.js` pour ajouter des interactions (pipette, export CSS, etc.).
+4. Lancer l’outil avec :
+
+```bash
+python3 app/tools/color-picker/run_color_picker.py
+```
+
+Une fois prêt, créez une entrée `minitools` dans `config/equipements.yaml` qui lance ce script (par exemple `command: "python3 /home/.../run_color_picker.py"`).
diff --git a/app/tools/color-picker/color_picker.py b/app/tools/color-picker/color_picker.py
new file mode 100755
index 0000000..dec8307
--- /dev/null
+++ b/app/tools/color-picker/color_picker.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+"""Popup GTK qui affiche l'interface Color Picker via WebKit"""
+import os
+import sys
+from pathlib import Path
+
+import gi
+
+# for systems blocking direct GBM access we force WebKit to stay in software mode
+os.environ.setdefault('WEBKIT_DISABLE_COMPOSITING_MODE', '1')
+os.environ.setdefault('GDK_BACKEND', 'x11')
+
+gi.require_version('Gtk', '3.0')
+gi.require_version('WebKit2', '4.1')
+from gi.repository import Gtk, WebKit2, GLib
+
+SCRIPT_DIR = Path(__file__).resolve().parent
+SRC_FILE = SCRIPT_DIR / 'src' / 'index.html'
+
+
+class ColorPickerWindow(Gtk.Window):
+ def __init__(self):
+ super().__init__(title='Color Picker')
+ self.set_default_size(980, 640)
+ self.set_position(Gtk.WindowPosition.CENTER)
+ self.set_border_width(8)
+ self.set_resizable(True)
+
+ settings = WebKit2.Settings()
+ settings.set_property('enable-developer-extras', False)
+ settings.set_property('enable-accelerated-2d-canvas', False)
+
+ self.webview = WebKit2.WebView()
+ self.webview.set_settings(settings)
+ self.add(self.webview)
+
+ uri = GLib.filename_to_uri(str(SRC_FILE), None)
+ self.webview.load_uri(uri)
+
+ self.connect('destroy', Gtk.main_quit)
+ self.show_all()
+
+
+def main() -> None:
+ if not SRC_FILE.exists():
+ print(f'Fichier introuvable : {SRC_FILE}', file=sys.stderr)
+ sys.exit(1)
+ win = ColorPickerWindow()
+ Gtk.main()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/app/tools/color-picker/palettes/default.json b/app/tools/color-picker/palettes/default.json
new file mode 100644
index 0000000..f3176c2
--- /dev/null
+++ b/app/tools/color-picker/palettes/default.json
@@ -0,0 +1,47 @@
+{
+ "palettes": [
+ {
+ "name": "Monokai Pro",
+ "description": "Palette chaude / turquoise",
+ "language": "CSS",
+ "colors": [
+ "#f8f8f2",
+ "#f92672",
+ "#fd971f",
+ "#a6e22e",
+ "#66d9ef",
+ "#9effff",
+ "#ae81ff",
+ "#ffffff"
+ ]
+ },
+ {
+ "name": "Solarized",
+ "description": "Teintes douces pour coding",
+ "language": "SCSS",
+ "colors": [
+ "#002b36",
+ "#073642",
+ "#586e75",
+ "#657b83",
+ "#839496",
+ "#b58900",
+ "#cb4b16",
+ "#dc322f"
+ ]
+ },
+ {
+ "name": "Material",
+ "description": "Couleurs saturées modernes",
+ "language": "CSS",
+ "colors": [
+ "#0f9d58",
+ "#f4b400",
+ "#4285f4",
+ "#db4437",
+ "#ab47bc",
+ "#00acc1"
+ ]
+ }
+ ]
+}
diff --git a/app/tools/color-picker/run_color_picker.py b/app/tools/color-picker/run_color_picker.py
new file mode 100755
index 0000000..e593b7a
--- /dev/null
+++ b/app/tools/color-picker/run_color_picker.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+"""Serve l'interface Color Picker depuis un mini-serveur HTTP local."""
+import http.server
+import socketserver
+import threading
+import webbrowser
+from pathlib import Path
+import signal
+import os
+
+PORT = int(os.environ.get('COLOR_PICKER_PORT', '9005'))
+BASE_DIR = Path(__file__).resolve().parent
+SRC_DIR = BASE_DIR / 'src'
+
+class ColorRequestHandler(http.server.SimpleHTTPRequestHandler):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, directory=str(BASE_DIR), **kwargs)
+
+
+def main() -> None:
+ with socketserver.TCPServer(('127.0.0.1', PORT), ColorRequestHandler) as httpd:
+ url = f'http://127.0.0.1:{PORT}/src/index.html'
+ print(f'Color Picker disponible sur {url}')
+ threading.Timer(0.3, lambda: webbrowser.open(url)).start()
+
+ def stop(signum, frame):
+ print('Arrêt du serveur Color Picker...')
+ httpd.shutdown()
+
+ signal.signal(signal.SIGINT, stop)
+ signal.signal(signal.SIGTERM, stop)
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ pass
+
+
+if __name__ == '__main__':
+ main()
diff --git a/app/tools/color-picker/src/app.js b/app/tools/color-picker/src/app.js
new file mode 100644
index 0000000..e6c1e27
--- /dev/null
+++ b/app/tools/color-picker/src/app.js
@@ -0,0 +1,159 @@
+const colorInput = document.getElementById('color-input');
+const selectedHexNode = document.getElementById('selected-hex');
+const complementHexNode = document.getElementById('complement-hex');
+const selectedColorBox = document.getElementById('selected-color');
+const complementColorBox = document.getElementById('complement-color');
+const paletteContainer = document.getElementById('palette-container');
+const themeSelector = document.getElementById('theme-selector');
+const themePreview = document.getElementById('theme-preview-content');
+const themeDescription = document.getElementById('theme-description');
+
+const paletteEndpoint = '../palettes/default.json';
+const themesEndpoint = '../themes/themes.json';
+let palettes = [];
+let themes = [];
+
+async function fetchPalettes() {
+ try {
+ const response = await fetch(paletteEndpoint);
+ const data = await response.json();
+ palettes = data.palettes || [];
+ renderPalettes(palettes);
+ } catch (err) {
+ paletteContainer.textContent = 'Impossible de charger les palettes.';
+ console.error(err);
+ }
+}
+
+async function fetchThemes() {
+ try {
+ const response = await fetch(themesEndpoint);
+ const data = await response.json();
+ themes = data.themes || [];
+ populateThemeSelector(themes);
+ } catch (err) {
+ themeDescription.textContent = 'Erreur lors du chargement des thèmes.';
+ console.error(err);
+ }
+}
+
+function populateThemeSelector(themeList) {
+ themeSelector.innerHTML = '';
+ themeList.forEach((theme, index) => {
+ const option = document.createElement('option');
+ option.value = theme.file;
+ option.textContent = theme.name;
+ themeSelector.appendChild(option);
+ if (index === 0) {
+ loadTheme(theme);
+ }
+ });
+}
+
+async function loadTheme(theme) {
+ try {
+ const response = await fetch(`../themes/${theme.file}`);
+ const content = await response.text();
+ themeDescription.textContent = theme.description || 'Aperçu CSS interactif.';
+ themePreview.textContent = content.trim();
+ } catch (err) {
+ themePreview.textContent = 'Impossible de charger le fichier.';
+ console.error(err);
+ }
+}
+
+function renderPalettes(list) {
+ paletteContainer.innerHTML = '';
+ list.forEach(palette => {
+ const card = document.createElement('article');
+ card.className = 'palette-card';
+ card.innerHTML = `
+
+ ${palette.name}
+ ${palette.description}
+
+
+
+ `;
+ const colorGrid = card.querySelector('.palette-colors');
+ palette.colors.forEach(color => {
+ const swatch = document.createElement('span');
+ swatch.style.background = color;
+ swatch.dataset.hex = color;
+ swatch.title = color;
+ swatch.addEventListener('click', () => updateColor(color));
+ colorGrid.appendChild(swatch);
+ });
+ const copyButton = card.querySelector('button');
+ copyButton.addEventListener('click', () => {
+ const text = palette.colors.join(', ');
+ copyToClipboard(text, copyButton);
+ });
+ paletteContainer.appendChild(card);
+ });
+}
+
+function updateColor(hex) {
+ const normalized = hex.startsWith('#') ? hex : `#${hex}`;
+ colorInput.value = normalized;
+ selectedHexNode.textContent = normalized;
+ selectedColorBox.style.background = normalized;
+ const complement = getComplement(normalized);
+ complementColorBox.style.background = complement;
+ complementHexNode.textContent = complement;
+}
+
+function getComplement(hex) {
+ const r = parseInt(hex.slice(1, 3), 16);
+ const g = parseInt(hex.slice(3, 5), 16);
+ const b = parseInt(hex.slice(5, 7), 16);
+ const complement = [255 - r, 255 - g, 255 - b]
+ .map(value => value.toString(16).padStart(2, '0'))
+ .join('');
+ return `#${complement}`;
+}
+
+function copyToClipboard(text, button) {
+ navigator.clipboard.writeText(text).then(() => {
+ if (button) {
+ const original = button.dataset.origText || button.textContent;
+ button.dataset.origText = original;
+ button.textContent = 'Copié';
+ button.classList.add('copied');
+ setTimeout(() => {
+ button.textContent = original;
+ button.classList.remove('copied');
+ }, 1200);
+ }
+ }).catch(err => {
+ console.error('Impossible de copier', err);
+ });
+}
+
+colorInput.addEventListener('input', (event) => {
+ updateColor(event.target.value);
+});
+
+document.querySelectorAll('button[data-copy]').forEach(button => {
+ button.addEventListener('click', () => {
+ const target = button.dataset.copy;
+ const text = target === 'selected' ? selectedHexNode.textContent : complementHexNode.textContent;
+ copyToClipboard(text, button);
+ });
+});
+
+themeSelector.addEventListener('change', () => {
+ const theme = themes.find(t => t.file === themeSelector.value);
+ if (theme) {
+ loadTheme(theme);
+ }
+});
+
+const themePanel = document.getElementById('theme-panel');
+document.getElementById('theme-toggle').addEventListener('click', () => {
+ themePanel.classList.toggle('collapsed');
+});
+
+fetchPalettes();
+fetchThemes();
+updateColor(colorInput.value);
diff --git a/app/tools/color-picker/src/index.html b/app/tools/color-picker/src/index.html
new file mode 100644
index 0000000..48eaf87
--- /dev/null
+++ b/app/tools/color-picker/src/index.html
@@ -0,0 +1,72 @@
+
+
+
+
+
+ Color Picker
+
+
+
+
+
+
+
+
+
+
+
Couleur active
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/tools/color-picker/src/style.css b/app/tools/color-picker/src/style.css
new file mode 100644
index 0000000..5b5f478
--- /dev/null
+++ b/app/tools/color-picker/src/style.css
@@ -0,0 +1,294 @@
+:root {
+ color-scheme: dark;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ min-height: 100vh;
+ font-family: 'Inter', 'Segoe UI', sans-serif;
+ background: radial-gradient(circle at top, #1a1a1a, #050505 75%);
+ color: #f6f6f6;
+}
+
+.app-shell {
+ display: grid;
+ grid-template-columns: 260px auto;
+ gap: 14px;
+ padding: 18px;
+ min-height: 100vh;
+}
+
+.theme-panel {
+ background: rgba(30, 30, 30, 0.95);
+ border: 1px solid #2f2f2f;
+ border-radius: 18px;
+ display: flex;
+ flex-direction: column;
+ padding: 14px;
+ transition: transform 200ms ease;
+ box-shadow: 0 20px 45px rgba(0, 0, 0, 0.55);
+}
+
+.theme-panel.collapsed {
+ transform: translateX(-228px);
+}
+
+.theme-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+}
+
+.theme-header button {
+ background: transparent;
+ border: 1px solid #3d3d3d;
+ color: #f6f6f6;
+ border-radius: 12px;
+ width: 36px;
+ height: 36px;
+ cursor: pointer;
+}
+
+.theme-header select {
+ background: #1c1f2b;
+ border: 1px solid #323248;
+ border-radius: 8px;
+ color: #f6f6f6;
+ padding: 6px 10px;
+}
+
+.theme-preview {
+ flex: 1;
+ background: linear-gradient(145deg, #1f1f28, #14141b);
+ border-radius: 12px;
+ border: 1px solid #353545;
+ padding: 10px;
+ overflow: auto;
+}
+
+.theme-preview pre {
+ font-size: 0.8rem;
+ white-space: pre-wrap;
+}
+
+.workspace {
+ background: rgba(15, 15, 24, 0.92);
+ border-radius: 28px;
+ border: 1px solid #1d1f38;
+ padding: 22px;
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+ box-shadow: 0 28px 45px rgba(6, 7, 32, 0.65);
+}
+
+.workspace-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: #f1f4ff;
+}
+
+.workspace-header h1 {
+ margin: 0;
+ font-size: 1.5rem;
+}
+
+.workspace-header p {
+ margin: 4px 0 0;
+ color: #a9aacf;
+}
+
+.window-actions button {
+ background: #26273a;
+ border: 1px solid #3d3d3d;
+ color: #fff;
+ width: 32px;
+ height: 32px;
+ margin-left: 6px;
+ border-radius: 8px;
+ cursor: pointer;
+}
+
+.color-panel {
+ display: flex;
+ gap: 32px;
+ align-items: center;
+ background: rgba(20, 20, 32, 0.95);
+ border-radius: 22px;
+ border: 1px solid #2f354f;
+ padding: 18px;
+ box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.35);
+}
+
+.color-wheel {
+ width: 220px;
+ height: 220px;
+ border-radius: 50%;
+ border: 5px solid rgba(255, 255, 255, 0.12);
+ background: conic-gradient(
+ #f94144,
+ #f3722c,
+ #f8961e,
+ #f9c74f,
+ #90be6d,
+ #43aa8b,
+ #4d908e,
+ #577590,
+ #277da1,
+ #4b4bfb,
+ #7209b7,
+ #f94144);
+ box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
+}
+
+.color-info {
+ flex: 1;
+ background: rgba(16, 16, 24, 0.92);
+ border-radius: 22px;
+ border: 1px solid #303046;
+ padding: 18px;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.section-label {
+ text-transform: uppercase;
+ letter-spacing: 0.2em;
+ font-size: 0.7rem;
+ color: #a3a7bc;
+ margin: 0;
+}
+
+.color-info input[type='color'] {
+ width: 100%;
+ height: 56px;
+ border-radius: 16px;
+ border: none;
+ cursor: pointer;
+}
+
+.color-swatch-row {
+ display: flex;
+ gap: 12px;
+}
+
+.color-box.large {
+ flex: 1;
+ height: 60px;
+ border-radius: 16px;
+ border: 1px solid #373a4f;
+ box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.6);
+}
+
+.color-metadata {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 0.85rem;
+}
+
+.color-metadata span {
+ font-size: 0.6rem;
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ color: #7c81ad;
+}
+
+.color-metadata strong {
+ margin-left: 8px;
+ font-size: 1.1rem;
+}
+
+.color-metadata button {
+ background: #2f72ff;
+ border: none;
+ border-radius: 8px;
+ color: #fff;
+ padding: 6px 12px;
+ cursor: pointer;
+ font-size: 0.72rem;
+}
+.color-metadata button.copied,
+.palette-card button.copied {
+ background: #4ccc97;
+ color: #0b2a1c;
+}
+
+.palette-section {
+ background: rgba(15, 15, 20, 0.9);
+ border-radius: 24px;
+ padding: 22px 18px 18px;
+ border: 1px solid #27293b;
+}
+
+.palette-section header {
+ margin-bottom: 22px;
+}
+
+.palette-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 12px;
+}
+
+.palette-card {
+ background: rgba(20, 20, 30, 0.9);
+ border-radius: 18px;
+ border: 1px solid #30305c;
+ padding: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.palette-card h3 {
+ margin: 0;
+ font-size: 1rem;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+}
+
+.palette-card .palette-colors {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 6px;
+}
+
+.palette-card .palette-colors span {
+ width: 100%;
+ padding-top: 100%;
+ border-radius: 10px;
+ border: 1px solid #1b1b25;
+}
+
+.palette-card small {
+ color: #9ea1c6;
+}
+
+.palette-card button {
+ border: none;
+ background: transparent;
+ color: #54f0b1;
+ cursor: pointer;
+ padding: 0;
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+@media (max-width: 960px) {
+ .app-shell {
+ grid-template-columns: 1fr;
+ }
+
+ .theme-panel {
+ order: 2;
+ }
+}
diff --git a/app/tools/color-picker/themes/monokai.css b/app/tools/color-picker/themes/monokai.css
new file mode 100644
index 0000000..212dc75
--- /dev/null
+++ b/app/tools/color-picker/themes/monokai.css
@@ -0,0 +1,16 @@
+:root {
+ --bg: #272822;
+ --fg: #f8f8f2;
+ --primary: #f92672;
+ --success: #a6e22e;
+}
+body {
+ background: var(--bg);
+ color: var(--fg);
+ font-family: 'Fira Code', 'JetBrains Mono', monospace;
+}
+button {
+ background: var(--primary);
+ color: var(--fg);
+ border: none;
+}
diff --git a/app/tools/color-picker/themes/solarized-light.css b/app/tools/color-picker/themes/solarized-light.css
new file mode 100644
index 0000000..c4dab0c
--- /dev/null
+++ b/app/tools/color-picker/themes/solarized-light.css
@@ -0,0 +1,13 @@
+:root {
+ --bg: #fdf6e3;
+ --fg: #657b83;
+ --primary: #268bd2;
+ --accent: #2aa198;
+}
+body {
+ background: var(--bg);
+ color: var(--fg);
+}
+header, .palette-card {
+ border-color: var(--accent);
+}
diff --git a/app/tools/color-picker/themes/themes.json b/app/tools/color-picker/themes/themes.json
new file mode 100644
index 0000000..b28e288
--- /dev/null
+++ b/app/tools/color-picker/themes/themes.json
@@ -0,0 +1,16 @@
+{
+ "themes": [
+ {
+ "name": "Monokai",
+ "file": "monokai.css",
+ "description": "Palette sombre inspirée de Monokai",
+ "type": ".css"
+ },
+ {
+ "name": "Solarized Light",
+ "file": "solarized-light.css",
+ "description": "Theme clair Solarized",
+ "type": ".css"
+ }
+ ]
+}
diff --git a/claude_prompt_ssh_app.md b/claude_prompt_ssh_app.md
new file mode 100644
index 0000000..c3b0b4e
--- /dev/null
+++ b/claude_prompt_ssh_app.md
@@ -0,0 +1,106 @@
+# Projet : Lanceur SSH via page web
+
+## Rôle de l’IA
+
+Tu es un expert en :
+- Linux (bureau + ligne de commande)
+- Intégration d’URL handlers (`x-scheme-handler`)
+- Développement web simple (HTML/CSS basique)
+- Organisation de projet et documentation (`README.md`)
+
+Tu m’aides à développer une petite application qui permet :
+1. D’afficher une page HTML avec une liste de machines (PC, serveurs, etc.).
+2. Pour chaque machine, un lien de type `ssh://user@ip` sur lequel je clique depuis le navigateur.
+3. Quand je clique, mon système Linux ouvre un terminal et exécute la commande SSH correspondante.
+
+## Objectif fonctionnel
+
+- Je veux pouvoir ouvrir un fichier HTML dans mon navigateur (Firefox/Chrome).
+- Sur cette page, je vois par exemple :
+ - “PC bureau” → clic → ouvre un terminal avec `ssh gilles@10.0.0.24`
+ - “Serveur Proxmox” → clic → ouvre un terminal avec `ssh root@10.0.0.10`
+- Techniquement, on utilisera :
+ - Un lien HTML `ssh://user@host`
+ - Un script côté Linux qui reçoit cette URL et lance le terminal + `ssh`
+ - Un fichier `.desktop` qui déclare le handler `x-scheme-handler/ssh`
+
+## Plateforme cible
+
+- OS : Linux (bureau, type Debian/Ubuntu ou équivalent)
+- Environnement : navigateur web + terminal graphique (gnome-terminal, xfce4-terminal, konsole, etc.)
+- Éditeur : VS Code avec Claude Code
+
+## Contraintes / préférences
+
+- Solution simple, sans serveur web complexe si possible :
+ - Une page HTML statique (`index.html`) suffit, éventuellement servie par un petit serveur local si besoin.
+- Le handler SSH doit :
+ - Prendre en entrée une URL de type `ssh://gilles@10.0.0.24`
+ - Extraire `gilles@10.0.0.24`
+ - Ouvrir un terminal graphique et lancer `ssh gilles@10.0.0.24`
+- Le projet doit contenir une documentation claire dans `README.md` :
+ - But du projet
+ - Pré-requis
+ - Installation / configuration du handler `ssh://`
+ - Exemple d’utilisation
+ - Comment ajouter/modifier des machines dans la page HTML
+
+## Architecture souhaitée du projet
+
+Propose et mets en place une structure simple, par exemple :
+
+- `index.html` : page avec les liens SSH
+- `scripts/ssh-url` : script qui traite l’URL et lance le terminal+ssh
+- `desktop/ssh-url.desktop` : fichier `.desktop` déclarant le handler `x-scheme-handler/ssh`
+- `README.md` : documentation du projet
+- (optionnel) `config/machines.yaml` ou `.json` si on veut générer la page HTML à partir d’une config
+
+Tu peux ajuster la structure si tu vois plus logique, mais garde quelque chose de simple et lisible.
+
+## Tâches à réaliser par l’IA
+
+1. **Conception**
+ - Décrire rapidement l’architecture retenue (fichiers, rôles, flux).
+ - Expliquer comment le navigateur → handler `ssh://` → script → terminal → ssh s’enchaînent.
+
+2. **Implémentation**
+ - Écrire :
+ - La page `index.html` avec quelques exemples de liens (`ssh://gilles@10.0.0.24`, etc.).
+ - Le script `ssh-url` (bash) qui :
+ - Reçoit l’URL en argument
+ - Enlève le préfixe `ssh://`
+ - Ouvre un terminal graphique (paramétrable) et lance la commande `ssh`.
+ - Le fichier `.desktop` `ssh-url.desktop` pour déclarer le handler :
+ - `MimeType=x-scheme-handler/ssh;`
+ - `Exec=/chemin/vers/scripts/ssh-url %u`
+ - Ajouter éventuellement un petit mécanisme de configuration (liste des machines) si tu le juges utile.
+
+3. **Instructions d’installation**
+ - Rédiger un `README.md` avec :
+ - Étapes d’installation détaillées
+ - Commandes à lancer (par ex. `chmod +x scripts/ssh-url`, `xdg-mime default ssh-url.desktop x-scheme-handler/ssh`, etc.)
+ - Comment tester : ouvrir `index.html` et cliquer sur un lien.
+
+4. **Portabilité**
+ - Expliquer clairement où je dois adapter :
+ - Le nom du terminal (`gnome-terminal`, `xfce4-terminal`, etc.)
+ - Les chemins dans `ssh-url.desktop`
+ - Les IP / users dans les liens `ssh://`.
+
+## Style de réponse attendu
+
+- Toujours proposer un **plan** avant de commencer les modifications de fichiers.
+- Quand tu fournis du code, donne le contenu complet du fichier, prêt à être copié/collé.
+- Utiliser du français clair, concis, avec des blocs de code bien séparés.
+
+## Commandes que j’utiliserai avec toi
+
+Je pourrai t’envoyer des messages de ce type :
+
+- `PLAN:` → tu proposes ou mets à jour le plan de travail.
+- `CREATE:` → tu crées les fichiers manquants ou les versions complètes.
+- `UPDATE:` → tu modifies les fichiers existants que je te montre.
+- `DOC:` → tu améliores ou complètes la documentation (`README.md`).
+
+Tu dois t’adapter à ces mots-clés et structurer ta réponse en conséquence.
+
diff --git a/config/equipements.yaml b/config/equipements.yaml
new file mode 100644
index 0000000..2109d0c
--- /dev/null
+++ b/config/equipements.yaml
@@ -0,0 +1,330 @@
+# Configuration des équipements et services
+# Modifiez ce fichier pour ajouter vos machines
+
+# =============================================================================
+# Configuration de la fenêtre popup
+# =============================================================================
+fenetre:
+ ecran: 0 # 0 = principal, 1 = secondaire, -1 = souris
+ centrer: false # Centrer horizontalement
+ #alignement: # center,left,right
+ #decalage_x: # decalage par rapport a l'alignement >0 pour left et center et <0 pour right
+ #decalage_y: # decalage par rapport a la barre du haut
+ x: 1500 # Position X (si centrer: false)
+ y: 0 # Position Y depuis le haut
+ largeur: 1100
+ hauteur: 820
+ fermer_sur_clic_exterieur: true
+ toujours_au_dessus: false
+ autohide: 1 # Fermer après X secondes si souris hors fenêtre (0 = désactivé)
+ section_distant:
+ colonne: 4
+ section_local:
+ colonne: 6
+ section_url:
+ colonne: 5
+
+# =============================================================================
+# Apparence
+# =============================================================================
+apparence:
+ theme: "dark" # "dark" ou "light"
+ police_taille: 14 # Taille de police en pixels
+ icon_taille_distant: 48 # Taille icônes section Distant
+ icon_taille_local: 52 # Taille icônes section Local
+ icon_taille_url: 48 # Taille icônes section URL
+ afficher_label_local: false # Afficher le texte sous les icônes locales
+ ping_intervalle: 360 # Intervalle entre les pings en secondes (0 = désactivé)
+ border_radius: 25 # Arrondi des coins de la fenêtre en pixels
+ espacement_local: 2 # Espacement entre icônes section Local (px)
+ espacement_distant: 6 # Espacement entre icônes section Distant (px)
+ espacement_url: 6 # Espacement entre icônes section URL (px)
+ icon_taille_fermer: 22 # Taille icône fermer (px)
+ icon_taille_parametre: 22 # Taille icône paramètre (px)
+ icon_taille_theme: 30 # Largeur icône night-day (px) - hauteur auto selon ratio
+ border_radius_local: 12 # Arrondi de la section locale (px)
+ # Couleurs personnalisées (laisser vide pour utiliser le thème par défaut)
+ couleur_fond: "#2a2a2a" # Fond principal (ex: "#1e1e1e")
+ couleur_header: "#2a2a2a" # Fond du header (ex: "#2a2a2a")
+ couleur_box_local: "#2a2a2a" # Fond section LOCAL (ex: "#3d3d3d")
+ couleur_box_distant: "#1e1e1e" # Fond boxes DISTANT (ex: "#3d3d3d")
+ couleur_box_url: "#3d3d3d" # Fond section URL (ex: "#3d3d3d")
+ couleur_item: "#1e1e1e" # Fond des items services (ex: "#2d2d2d")
+ couleur_bordure: "#111a1a" # Couleur des bordures (ex: "#1a1a1a")
+ couleur_hover: "#515555" # Couleur au survol souris (ex: "#555555")
+ icon_taille_minitools: 52 # Taille des icônes de la barre mini-tools
+ espacement_minitools: 6 # Espacement entre les mini outils (px)
+ couleur_box_minitools: "#252525" # Fond de la barre mini tools (optionnel)
+
+# =============================================================================
+# Section DISTANT - Machines distantes (à gauche)
+# =============================================================================
+distant:
+ - ip: "10.0.0.24"
+ nom: "PC Dashboard"
+ services:
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://gilles@10.0.0.24"
+ - nom: "VNC"
+ icon: "icons/terminal.png"
+ url: "vnc://10.0.0.24:5900"
+
+ - ip: "10.0.1.232"
+ nom: "m710q"
+ services:
+ - nom: "proxmox"
+ icon: "icons/proxmox.png"
+ url: "https://10.0.1.232:8006"
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://root@10.0.1.232"
+
+ - ip: "10.0.0.101"
+ nom: "EliteDesk"
+ services:
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://root@10.0.0.101"
+ - nom: "Proxmox"
+ icon: "icons/proxmox.png"
+ url: "https://10.0.0.101:8006/"
+
+ - ip: "10.0.0.5"
+ nom: "VM 5"
+ services:
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://gilles@10.0.0.5"
+ password: "gilles" #copy password to clipboard
+ - nom: "Gitea"
+ icon: "icons/gitea.png"
+ url: "https://gitea.maison43.duckdns.org/"
+ - nom: "Arcane"
+ icon: "icons/arcane.png"
+ url: "http://10.0.0.5:3552/"
+ - nom: "Hortus_fox"
+ icon: "icons/hortusfox.png"
+ url: "http://10.0.0.5:8081/"
+ - nom: "Termix"
+ icon: "icons/termix.png"
+ url: "http://10.0.0.5:6080/"
+ - nom: "upsnap"
+ icon: "icons/upsnap.png"
+ url: "http://10.0.0.5:8090/"
+ - nom: "samba" # automount samba in nautilus and open in nautilus
+ icon: "icons/smb.png"
+ url: "smb://10.0.0.5/"
+ user: gilles #user for samba
+ password: gilles #password for samba
+
+
+
+ - ip: "137.74.45.99"
+ nom: "Vps OVH"
+ services:
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://debian@137.74.45.99"
+ password: "Misstibet5*" #copy password to clipboard
+ - nom: "VNC"
+ icon: "icons/terminal.png"
+ url: "vnc://137.74.45.99:5901"
+
+ - ip: "10.0.0.2"
+ nom: "Home Assistant"
+ services:
+ - nom: "Home Assistant"
+ icon: "icons/home-assistant.png"
+ url: "http://10.0.0.2:8123"
+
+ - ip: "10.0.0.205"
+ nom: "Proliant"
+ services:
+ - nom: "Proxmox"
+ icon: "icons/proxmox.png"
+ url: "https://10.0.0.205:8006/"
+
+ - ip: "10.0.0.15"
+ nom: "Adguard"
+ services:
+ - nom: "Adguard"
+ icon: "icons/adguard-home.png"
+ url: "http://10.0.0.15/"
+
+ - ip: "10.0.0.135"
+ nom: "Paperless"
+ services:
+ - nom: "Paperless"
+ icon: "icons/paperless.png"
+ url: "http://10.0.0.135:8777/"
+ - nom: "scanServ.js"
+ icon: "icons/scanservjs.png"
+ url: "http://10.0.0.135:8080/"
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://gilles@10.0.0.135"
+ password: "gilles" #copy password to clipboard
+
+ - ip: "10.0.0.116"
+ nom: "VM 116"
+ services:
+ - nom: "Arcane"
+ icon: "icons/arcane.png"
+ url: "http://10.0.0.116:3552/"
+ - nom: "Heimdall"
+ icon: "icons/heimdall.png"
+ url: "http://10.0.0.135:8002/"
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://gilles@10.0.0.116"
+ password: "gilles" #copy password to clipboard
+
+# =============================================================================
+# Section LOCAL - Applications locales (en bas)
+# =============================================================================
+local:
+ - nom: "Fichiers"
+ icon: "icons/nautilus.png"
+ command: "nautilus"
+ - nom: "Terminal"
+ icon: "icons/terminal.png"
+ command: "gnome-terminal"
+ - nom: "Firefox"
+ icon: "icons/firefox.png"
+ command: "firefox"
+ - nom: "Chrome"
+ icon: "icons/chrome.png"
+ command: "google-chrome"
+ - nom: "VS Code"
+ icon: "icons/vscode.png"
+ command: "code"
+ - nom: "Calculatrice"
+ icon: "icons/calculatrice.png"
+ command: "gnome-calculator"
+ - nom: "Gnumeric"
+ icon: "icons/gnumeric.png"
+ command: "gnumeric"
+ - nom: "Moniteur system"
+ icon: "icons/gparameter.png"
+ command: "gnome-control-center"
+ - nom: "Parametres"
+ icon: "icons/monitor.png"
+ command: "gnome-system-monitor"
+
+# =============================================================================
+# Section URL - Favoris web (à droite)
+# =============================================================================
+url:
+ - nom: "Google"
+ icon: "icons/google.png"
+ url: "https://www.google.com/"
+ - nom: "Gmail"
+ icon: "icons/gmail.png"
+ url: "https://mail.google.com"
+ - nom: "YouTube"
+ icon: "icons/youtube.png"
+ url: "https://youtube.com"
+ - nom: "Dokuwiki"
+ icon: "icons/dokuwiki.png"
+ url: "http://10.0.0.5:8880/"
+ - nom: "Heimdall"
+ icon: "icons/heimdall.png"
+ url: "http://10.0.0.5:8002/"
+ - nom: "Claude"
+ icon: "icons/claude.png"
+ url: "https://claude.ai/"
+ - nom: "Chatgpt"
+ icon: "icons/chatgpt.png"
+ url: "https://chatgpt.com/"
+ - nom: "Vaultwarden"
+ icon: "icons/vaultwarden.png"
+ url: "https://vw.maison43.duckdns.org"
+ - nom: "Digiposte"
+ icon: "icons/digiposte.png"
+ url: "https://moncompte.laposte.fr/"
+ - nom: "Google Actu"
+ icon: "icons/googlenews.png"
+ url: "https://news.google.com/"
+ - nom: "Z2M ETH"
+ icon: "icons/zigbee2mqtt.png"
+ url: "http://10.0.0.105:8080"
+ - nom: "Z2M"
+ icon: "icons/zigbee2mqtt2.png"
+ url: "http://10.0.0.106:8080"
+ - nom: "nodered"
+ icon: "icons/nodered.png"
+ url: "http://10.0.0.9:1880/"
+ - nom: "memos"
+ icon: "icons/memos.png"
+ url: "http://10.0.0.5:5230/"
+ - nom: "gemini"
+ icon: "icons/gemini.png"
+ url: "https://gemini.google.com/app"
+ - nom: "Mistral"
+ icon: "icons/mistral.png"
+ url: "https://chat.mistral.ai/chat"
+ - nom: "Grok"
+ icon: "icons/grok.png"
+ url: "https://chat.mistral.ai/chat"
+ - nom: "Nginx Proxy Manager"
+ icon: "icons/nginx-proxy-manager.png"
+ url: "http://10.0.0.202:81/"
+ - nom: "selfhosted"
+ icon: "icons/selfh-st.png"
+ url: "https://selfh.st/"
+ - nom: "korben"
+ icon: "icons/korben.jpeg"
+ url: "https://korben.info/"
+ - nom: "amazon"
+ icon: "icons/amazon.png"
+ url: "https://www.amazon.fr/"
+ - nom: "prime"
+ icon: "icons/amazon-prime-video.png"
+ url: "https://www.primevideo.com/"
+ - nom: "Orange TV"
+ icon: "icons/orange_tv.png"
+ url: "https://tv.orange.fr/en-direct/programmes-en-cours"
+ - nom: "La Commère 43"
+ icon: "icons/comere43.jpeg"
+ url: "https://www.lacommere43.fr/fait-divers.html"
+ - nom: "Aliexpress"
+ icon: "icons/aliexpress.png"
+ url: "https://fr.aliexpress.com"
+ - nom: "Google Drive"
+ icon: "icons/gdrive.png"
+ url: "https://drive.google.com/"
+ - nom: "Camera detect"
+ icon: "icons/camera.png"
+ url: "http://10.0.0.13:8081/"
+ - nom: "Camera detect"
+ icon: "icons/camera.png"
+ url: "http://10.0.0.13:8081/"
+
+# =============================================================================
+# Section MINITOOLS - barre d'outils rapides (ligne en bas)
+# =============================================================================
+minitools:
+ # indiquez soit un "command" (sera lancé via app://run/) soit une "url"
+ - nom: "Immich"
+ icon: "icons/immich.png"
+ url: "http://10.0.0.30:2283"
+ - nom: "MQTT Explorer"
+ icon: "icons/mqttexplorer.png"
+ url: "http://10.0.0.8:8088/"
+ - nom: "Hardware Benchtools"
+ icon: "icons/hardware_benchtools.png"
+ url: "http://10.0.0.50:8087"
+ - nom: "Excalidraw"
+ icon: "icons/excalidraw.png"
+ url: "http://10.0.1.123:3000/"
+ - nom: "IPWatch"
+ icon: "icons/ipwatch.png"
+ url: "http://10.0.0.8:8080/"
+ - nom: "VideoPlayer"
+ icon: "icons/videoplayer.png"
+ url: "http://10.0.0.50:8080/"
+
+ - nom: "Palette couleurs"
+ icon: "icons/color-picker.svg"
+ command: "python3 /home/gilles/Documents/vscode/ssh-web-launcher/app/tools/color-picker/color_picker.py"
diff --git a/config/equipements.yaml.yml b/config/equipements.yaml.yml
new file mode 100644
index 0000000..f936619
--- /dev/null
+++ b/config/equipements.yaml.yml
@@ -0,0 +1,296 @@
+# Configuration des équipements et services
+# Modifiez ce fichier pour ajouter vos machines
+
+# =============================================================================
+# Configuration de la fenêtre popup
+# =============================================================================
+fenetre:
+ ecran: 0 # 0 = principal, 1 = secondaire, -1 = souris
+ centrer: false # Centrer horizontalement
+ #alignement: # center,left,right
+ #decalage_x: # decalage par rapport a l'alignement >0 pour left et center et <0 pour right
+ #decalage_y: # decalage par rapport a la barre du haut
+ x: 1500 # Position X (si centrer: false)
+ y: 0 # Position Y depuis le haut
+ largeur: 1100
+ hauteur: 840
+ fermer_sur_clic_exterieur: true
+ toujours_au_dessus: false
+ autohide: 1 # Fermer après X secondes si souris hors fenêtre (0 = désactivé)
+ section_distant:
+ colonne: 4
+ section_local:
+ colonne: 6
+ section_url:
+ colonne: 5
+
+# =============================================================================
+# Apparence
+# =============================================================================
+apparence:
+ theme: "dark" # "dark" ou "light"
+ police_taille: 14 # Taille de police en pixels
+ icon_taille_distant: 48 # Taille icônes section Distant
+ icon_taille_local: 52 # Taille icônes section Local
+ icon_taille_url: 48 # Taille icônes section URL
+ afficher_label_local: false # Afficher le texte sous les icônes locales
+ ping_intervalle: 360 # Intervalle entre les pings en secondes (0 = désactivé)
+ border_radius: 25 # Arrondi des coins de la fenêtre en pixels
+ espacement_local: 2 # Espacement entre icônes section Local (px)
+ espacement_distant: 6 # Espacement entre icônes section Distant (px)
+ espacement_url: 6 # Espacement entre icônes section URL (px)
+ icon_taille_fermer: 22 # Taille icône fermer (px)
+ icon_taille_parametre: 22 # Taille icône paramètre (px)
+ icon_taille_theme: 30 # Largeur icône night-day (px) - hauteur auto selon ratio
+ border_radius_local: 12 # Arrondi de la section locale (px)
+ # Couleurs personnalisées (laisser vide pour utiliser le thème par défaut)
+ couleur_fond: "#2a2a2a" # Fond principal (ex: "#1e1e1e")
+ couleur_header: "#2a2a2a" # Fond du header (ex: "#2a2a2a")
+ couleur_box_local: "#2a2a2a" # Fond section LOCAL (ex: "#3d3d3d")
+ couleur_box_distant: "#1e1e1e" # Fond boxes DISTANT (ex: "#3d3d3d")
+ couleur_box_url: "#3d3d3d" # Fond section URL (ex: "#3d3d3d")
+ couleur_item: "#1e1e1e" # Fond des items services (ex: "#2d2d2d")
+ couleur_bordure: "#111a1a" # Couleur des bordures (ex: "#1a1a1a")
+ couleur_hover: "#515555" # Couleur au survol souris (ex: "#555555")
+
+# =============================================================================
+# Section DISTANT - Machines distantes (à gauche)
+# =============================================================================
+distant:
+ - ip: "10.0.0.24"
+ nom: "PC Dashboard"
+ services:
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://gilles@10.0.0.24"
+ - nom: "VNC"
+ icon: "icons/terminal.png"
+ url: "vnc://10.0.0.24:5900"
+
+ - ip: "10.0.1.232"
+ nom: "m710q"
+ services:
+ - nom: "proxmox"
+ icon: "icons/proxmox.png"
+ url: "https://10.0.1.232:8006"
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://root@10.0.1.232"
+
+ - ip: "10.0.0.101"
+ nom: "EliteDesk"
+ services:
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://root@10.0.0.101"
+ - nom: "Proxmox"
+ icon: "icons/proxmox.png"
+ url: "https://10.0.0.101:8006/"
+
+ - ip: "10.0.0.5"
+ nom: "VM 5"
+ services:
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://gilles@10.0.0.5"
+ password: "gilles" #copy password to clipboard
+ - nom: "Gitea"
+ icon: "icons/gitea.png"
+ url: "https://gitea.maison43.duckdns.org/"
+ - nom: "Arcane"
+ icon: "icons/arcane.png"
+ url: "http://10.0.0.5:3552/"
+ - nom: "Hortus_fox"
+ icon: "icons/hortusfox.png"
+ url: "http://10.0.0.5:8081/"
+ - nom: "Termix"
+ icon: "icons/termix.png"
+ url: "http://10.0.0.5:6080/"
+ - nom: "upsnap"
+ icon: "icons/upsnap.png"
+ url: "http://10.0.0.5:8090/"
+ - nom: "samba" # automount samba in nautilus and open in nautilus
+ icon: "icons/smb.png"
+ url: "smb://10.0.0.5/"
+ user: gilles #user for samba
+ password: gilles #password for samba
+
+
+
+ - ip: "137.74.45.99"
+ nom: "Vps OVH"
+ services:
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://debian@137.74.45.99"
+ password: "Misstibet5*" #copy password to clipboard
+ - nom: "VNC"
+ icon: "icons/terminal.png"
+ url: "vnc://137.74.45.99:5901"
+
+ - ip: "10.0.0.2"
+ nom: "Home Assistant"
+ services:
+ - nom: "Home Assistant"
+ icon: "icons/home-assistant.png"
+ url: "http://10.0.0.2:8123"
+
+ - ip: "10.0.0.205"
+ nom: "Proliant"
+ services:
+ - nom: "Proxmox"
+ icon: "icons/proxmox.png"
+ url: "https://10.0.0.205:8006/"
+
+ - ip: "10.0.0.15"
+ nom: "Adguard"
+ services:
+ - nom: "Adguard"
+ icon: "icons/adguard-home.png"
+ url: "http://10.0.0.15/"
+
+ - ip: "10.0.0.135"
+ nom: "Paperless"
+ services:
+ - nom: "Paperless"
+ icon: "icons/paperless.png"
+ url: "http://10.0.0.135:8777/"
+ - nom: "scanServ.js"
+ icon: "icons/scanservjs.png"
+ url: "http://10.0.0.135:8080/"
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://gilles@10.0.0.135"
+ password: "gilles" #copy password to clipboard
+
+ - ip: "10.0.0.116"
+ nom: "VM 116"
+ services:
+ - nom: "Arcane"
+ icon: "icons/arcane.png"
+ url: "http://10.0.0.116:3552/"
+ - nom: "Heimdall"
+ icon: "icons/heimdall.png"
+ url: "http://10.0.0.135:8002/"
+ - nom: "SSH"
+ icon: "icons/ssh.png"
+ url: "ssh://gilles@10.0.0.116"
+ password: "gilles" #copy password to clipboard
+
+# =============================================================================
+# Section LOCAL - Applications locales (en bas)
+# =============================================================================
+local:
+ - nom: "Fichiers"
+ icon: "icons/nautilus.png"
+ command: "nautilus"
+ - nom: "Terminal"
+ icon: "icons/terminal.png"
+ command: "gnome-terminal"
+ - nom: "Firefox"
+ icon: "icons/firefox.png"
+ command: "firefox"
+ - nom: "Chrome"
+ icon: "icons/chrome.png"
+ command: "google-chrome"
+ - nom: "VS Code"
+ icon: "icons/vscode.png"
+ command: "code"
+ - nom: "Calculatrice"
+ icon: "icons/calculatrice.png"
+ command: "gnome-calculator"
+ - nom: "Gnumeric"
+ icon: "icons/gnumeric.png"
+ command: "gnumeric"
+ - nom: "Moniteur system"
+ icon: "icons/gparameter.png"
+ command: "gnome-control-center"
+ - nom: "Parametres"
+ icon: "icons/monitor.png"
+ command: "gnome-system-monitor"
+
+# =============================================================================
+# Section URL - Favoris web (à droite)
+# =============================================================================
+url:
+ - nom: "Google"
+ icon: "icons/google.png"
+ url: "https://www.google.com/"
+ - nom: "Gmail"
+ icon: "icons/gmail.png"
+ url: "https://mail.google.com"
+ - nom: "YouTube"
+ icon: "icons/youtube.png"
+ url: "https://youtube.com"
+ - nom: "Dokuwiki"
+ icon: "icons/dokuwiki.png"
+ url: "http://10.0.0.5:8880/"
+ - nom: "Heimdall"
+ icon: "icons/heimdall.png"
+ url: "http://10.0.0.5:8002/"
+ - nom: "Claude"
+ icon: "icons/claude.png"
+ url: "https://claude.ai/"
+ - nom: "Chatgpt"
+ icon: "icons/chatgpt.png"
+ url: "https://chatgpt.com/"
+ - nom: "Vaultwarden"
+ icon: "icons/vaultwarden.png"
+ url: "https://vw.maison43.duckdns.org"
+ - nom: "Digiposte"
+ icon: "icons/digiposte.png"
+ url: "https://moncompte.laposte.fr/"
+ - nom: "Google Actu"
+ icon: "icons/googlenews.png"
+ url: "https://news.google.com/"
+ - nom: "Z2M ETH"
+ icon: "icons/zigbee2mqtt.png"
+ url: "http://10.0.0.105:8080"
+ - nom: "Z2M"
+ icon: "icons/zigbee2mqtt2.png"
+ url: "http://10.0.0.106:8080"
+ - nom: "nodered"
+ icon: "icons/nodered.png"
+ url: "http://10.0.0.9:1880/"
+ - nom: "memos"
+ icon: "icons/memos.png"
+ url: "http://10.0.0.5:5230/"
+ - nom: "gemini"
+ icon: "icons/gemini.png"
+ url: "https://gemini.google.com/app"
+ - nom: "Mistral"
+ icon: "icons/mistral.png"
+ url: "https://chat.mistral.ai/chat"
+ - nom: "Grok"
+ icon: "icons/grok.png"
+ url: "https://chat.mistral.ai/chat"
+ - nom: "Nginx Proxy Manager"
+ icon: "icons/nginx-proxy-manager.png"
+ url: "http://10.0.0.202:81/"
+ - nom: "selfhosted"
+ icon: "icons/selfh-st.png"
+ url: "https://selfh.st/"
+ - nom: "korben"
+ icon: "icons/korben.jpeg"
+ url: "https://korben.info/"
+ - nom: "amazon"
+ icon: "icons/amazon.png"
+ url: "https://www.amazon.fr/"
+ - nom: "prime"
+ icon: "icons/amazon-prime-video.png"
+ url: "https://www.primevideo.com/"
+ - nom: "Orange TV"
+ icon: "icons/orange_tv.png"
+ url: "https://tv.orange.fr/en-direct/programmes-en-cours"
+ - nom: "La Commère 43"
+ icon: "icons/comere43.jpeg"
+ url: "https://www.lacommere43.fr/fait-divers.html"
+ - nom: "Aliexpress"
+ icon: "icons/aliexpress.png"
+ url: "https://fr.aliexpress.com"
+ - nom: "Google Drive"
+ icon: "icons/gdrive.png"
+ url: "https://drive.google.com/"
+ - nom: "Camera detect"
+ icon: "icons/camera.png"
+ url: "http://10.0.0.13:8081/"
diff --git a/dashboard.drawio b/dashboard.drawio
new file mode 100644
index 0000000..a26edd8
--- /dev/null
+++ b/dashboard.drawio
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/desktop/ssh-url.desktop b/desktop/ssh-url.desktop
new file mode 100644
index 0000000..1dbd677
--- /dev/null
+++ b/desktop/ssh-url.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Name=SSH URL Handler
+Comment=Ouvre les liens ssh:// dans un terminal
+Exec=/home/gilles/Documents/vscode/ssh-web-launcher/scripts/ssh-url %u
+Type=Application
+Terminal=false
+NoDisplay=true
+MimeType=x-scheme-handler/ssh;
+Categories=Network;
diff --git a/extension/extension.js b/extension/extension.js
new file mode 100644
index 0000000..01cc69e
--- /dev/null
+++ b/extension/extension.js
@@ -0,0 +1,111 @@
+/* extension.js
+ * Extension GNOME Shell - Dashboard Launcher
+ * Ajoute une icône dans la barre supérieure pour lancer/fermer le Dashboard
+ * Toggle: clic = affiche, re-clic = ferme
+ *
+ * Compatible GNOME 45+ (ESM)
+ */
+
+'use strict';
+
+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';
+
+const APP_PATH = GLib.build_filenamev(['/home/gilles/Documents/vscode/ssh-web-launcher', 'app', 'ssh-launcher-gtk.py']);
+const PID_FILE = '/tmp/dashboard-launcher.pid';
+
+const DashboardLauncherIndicator = GObject.registerClass(
+class DashboardLauncherIndicator extends PanelMenu.Button {
+ _init() {
+ super._init(0.0, 'Dashboard Launcher');
+
+ // Icône dans la barre (style dashboard/grille)
+ this._icon = new St.Icon({
+ icon_name: 'view-app-grid-symbolic',
+ style_class: 'system-status-icon dashboard-launcher-icon',
+ });
+
+ this.add_child(this._icon);
+
+ // Connexion du clic
+ this.connect('button-press-event', this._onClick.bind(this));
+ }
+
+ _onClick() {
+ this._toggleApp();
+ return true;
+ }
+
+ _isRunning() {
+ // Vérifie si le dashboard est en cours d'exécution
+ try {
+ let file = Gio.File.new_for_path(PID_FILE);
+ if (file.query_exists(null)) {
+ let [success, contents] = file.load_contents(null);
+ if (success) {
+ let pid = parseInt(new TextDecoder().decode(contents).trim());
+ // Vérifier si le processus existe (signal 0)
+ try {
+ let checkProc = new Gio.Subprocess({
+ argv: ['kill', '-0', pid.toString()],
+ flags: Gio.SubprocessFlags.NONE,
+ });
+ checkProc.init(null);
+ checkProc.wait(null);
+ return checkProc.get_successful();
+ } catch (e) {
+ return false;
+ }
+ }
+ }
+ } catch (e) {
+ // Fichier n'existe pas ou erreur de lecture
+ }
+ return false;
+ }
+
+ _toggleApp() {
+ try {
+ if (this._isRunning()) {
+ // Dashboard ouvert -> le fermer avec --toggle
+ let subprocess = new Gio.Subprocess({
+ argv: ['python3', APP_PATH, '--toggle'],
+ flags: Gio.SubprocessFlags.NONE,
+ });
+ subprocess.init(null);
+ } else {
+ // Dashboard fermé -> l'ouvrir
+ let subprocess = new Gio.Subprocess({
+ argv: ['python3', APP_PATH],
+ flags: Gio.SubprocessFlags.NONE,
+ });
+ subprocess.init(null);
+ }
+ } catch (e) {
+ logError(e, 'Dashboard Launcher: Erreur lors du toggle');
+ }
+ }
+});
+
+export default class DashboardLauncherExtension {
+ constructor() {
+ this._indicator = null;
+ }
+
+ enable() {
+ this._indicator = new DashboardLauncherIndicator();
+ Main.panel.addToStatusArea('dashboard-launcher', this._indicator);
+ }
+
+ disable() {
+ if (this._indicator) {
+ this._indicator.destroy();
+ this._indicator = null;
+ }
+ }
+}
diff --git a/extension/metadata.json b/extension/metadata.json
new file mode 100644
index 0000000..bff0c96
--- /dev/null
+++ b/extension/metadata.json
@@ -0,0 +1,8 @@
+{
+ "name": "SSH Launcher",
+ "description": "Lance une interface de connexion SSH depuis la barre GNOME",
+ "uuid": "ssh-launcher@local",
+ "version": 1,
+ "shell-version": ["42", "43", "44", "45", "46", "47", "48"],
+ "url": ""
+}
diff --git a/extension/stylesheet.css b/extension/stylesheet.css
new file mode 100644
index 0000000..bea1c93
--- /dev/null
+++ b/extension/stylesheet.css
@@ -0,0 +1,10 @@
+/* stylesheet.css - Style de l'extension SSH Launcher */
+
+.ssh-launcher-icon {
+ /* Icône légèrement colorée pour la distinguer */
+ color: #00d9ff;
+}
+
+.ssh-launcher-icon:hover {
+ color: #ffffff;
+}
diff --git a/icons original/amazon-prime-video.png b/icons original/amazon-prime-video.png
new file mode 100644
index 0000000..7b474aa
Binary files /dev/null and b/icons original/amazon-prime-video.png differ
diff --git a/icons original/amazon.png b/icons original/amazon.png
new file mode 100644
index 0000000..f1c81ab
Binary files /dev/null and b/icons original/amazon.png differ
diff --git a/icons original/arcane.png b/icons original/arcane.png
new file mode 100644
index 0000000..e07efda
Binary files /dev/null and b/icons original/arcane.png differ
diff --git a/icons original/calculatrice.png b/icons original/calculatrice.png
new file mode 100644
index 0000000..b94cf26
Binary files /dev/null and b/icons original/calculatrice.png differ
diff --git a/icons original/chatgpt.png b/icons original/chatgpt.png
new file mode 100644
index 0000000..d9b137a
Binary files /dev/null and b/icons original/chatgpt.png differ
diff --git a/icons original/chrome.png b/icons original/chrome.png
new file mode 100644
index 0000000..cd86adb
Binary files /dev/null and b/icons original/chrome.png differ
diff --git a/icons original/claude.png b/icons original/claude.png
new file mode 100644
index 0000000..50d96af
Binary files /dev/null and b/icons original/claude.png differ
diff --git a/icons original/digiposte.png b/icons original/digiposte.png
new file mode 100644
index 0000000..5e0b014
Binary files /dev/null and b/icons original/digiposte.png differ
diff --git a/icons original/dokuwiki.png b/icons original/dokuwiki.png
new file mode 100644
index 0000000..6231734
Binary files /dev/null and b/icons original/dokuwiki.png differ
diff --git a/icons original/excalidraw.png b/icons original/excalidraw.png
new file mode 100644
index 0000000..853b3d4
Binary files /dev/null and b/icons original/excalidraw.png differ
diff --git a/icons original/fermer.png b/icons original/fermer.png
new file mode 100644
index 0000000..bb47fb5
Binary files /dev/null and b/icons original/fermer.png differ
diff --git a/icons original/fermer2.png b/icons original/fermer2.png
new file mode 100644
index 0000000..ec5e599
Binary files /dev/null and b/icons original/fermer2.png differ
diff --git a/icons original/firefox.png b/icons original/firefox.png
new file mode 100644
index 0000000..081de12
Binary files /dev/null and b/icons original/firefox.png differ
diff --git a/icons original/gemini.png b/icons original/gemini.png
new file mode 100644
index 0000000..15aaef4
Binary files /dev/null and b/icons original/gemini.png differ
diff --git a/icons original/gitea.png b/icons original/gitea.png
new file mode 100644
index 0000000..1e0d56e
Binary files /dev/null and b/icons original/gitea.png differ
diff --git a/icons original/gmail.png b/icons original/gmail.png
new file mode 100644
index 0000000..3b73d57
Binary files /dev/null and b/icons original/gmail.png differ
diff --git a/icons original/gnumeric.png b/icons original/gnumeric.png
new file mode 100644
index 0000000..4514192
Binary files /dev/null and b/icons original/gnumeric.png differ
diff --git a/icons original/google.png b/icons original/google.png
new file mode 100644
index 0000000..c23eed0
Binary files /dev/null and b/icons original/google.png differ
diff --git a/icons original/googlenews.png b/icons original/googlenews.png
new file mode 100644
index 0000000..a7cf45a
Binary files /dev/null and b/icons original/googlenews.png differ
diff --git a/icons original/gparameter.png b/icons original/gparameter.png
new file mode 100644
index 0000000..b4ef233
Binary files /dev/null and b/icons original/gparameter.png differ
diff --git a/icons original/grok.png b/icons original/grok.png
new file mode 100644
index 0000000..1ba65e2
Binary files /dev/null and b/icons original/grok.png differ
diff --git a/icons original/hardware_benchtools.png b/icons original/hardware_benchtools.png
new file mode 100644
index 0000000..f4f97bf
Binary files /dev/null and b/icons original/hardware_benchtools.png differ
diff --git a/icons original/heimdall.png b/icons original/heimdall.png
new file mode 100644
index 0000000..299fed3
Binary files /dev/null and b/icons original/heimdall.png differ
diff --git a/icons original/home-assistant.png b/icons original/home-assistant.png
new file mode 100644
index 0000000..fc6b190
Binary files /dev/null and b/icons original/home-assistant.png differ
diff --git a/icons original/hortusfox.png b/icons original/hortusfox.png
new file mode 100644
index 0000000..fd72f9e
Binary files /dev/null and b/icons original/hortusfox.png differ
diff --git a/icons original/immich.png b/icons original/immich.png
new file mode 100644
index 0000000..b5f16a1
Binary files /dev/null and b/icons original/immich.png differ
diff --git a/icons original/ipwatch.png b/icons original/ipwatch.png
new file mode 100644
index 0000000..1475850
Binary files /dev/null and b/icons original/ipwatch.png differ
diff --git a/icons original/memos.png b/icons original/memos.png
new file mode 100644
index 0000000..17be6b7
Binary files /dev/null and b/icons original/memos.png differ
diff --git a/icons original/monitor.png b/icons original/monitor.png
new file mode 100644
index 0000000..43e30c5
Binary files /dev/null and b/icons original/monitor.png differ
diff --git a/icons original/mqttexplorer.png b/icons original/mqttexplorer.png
new file mode 100644
index 0000000..b381802
Binary files /dev/null and b/icons original/mqttexplorer.png differ
diff --git a/icons original/nautilus.png b/icons original/nautilus.png
new file mode 100644
index 0000000..82e36bb
Binary files /dev/null and b/icons original/nautilus.png differ
diff --git a/icons original/nfs.png b/icons original/nfs.png
new file mode 100644
index 0000000..206bdce
Binary files /dev/null and b/icons original/nfs.png differ
diff --git a/icons original/nginx-proxy-manager.png b/icons original/nginx-proxy-manager.png
new file mode 100644
index 0000000..ad0854b
Binary files /dev/null and b/icons original/nginx-proxy-manager.png differ
diff --git a/icons original/night-day.png b/icons original/night-day.png
new file mode 100644
index 0000000..bf1c1b7
Binary files /dev/null and b/icons original/night-day.png differ
diff --git a/icons original/night-day2.png b/icons original/night-day2.png
new file mode 100644
index 0000000..3bbb722
Binary files /dev/null and b/icons original/night-day2.png differ
diff --git a/icons original/nodered.png b/icons original/nodered.png
new file mode 100644
index 0000000..cc7eb0d
Binary files /dev/null and b/icons original/nodered.png differ
diff --git a/icons original/ollama.png b/icons original/ollama.png
new file mode 100644
index 0000000..54c1c2b
Binary files /dev/null and b/icons original/ollama.png differ
diff --git a/icons original/paperless.png b/icons original/paperless.png
new file mode 100644
index 0000000..a9ac9cb
Binary files /dev/null and b/icons original/paperless.png differ
diff --git a/icons original/parameter.png b/icons original/parameter.png
new file mode 100644
index 0000000..7f618b4
Binary files /dev/null and b/icons original/parameter.png differ
diff --git a/icons original/parametre.png b/icons original/parametre.png
new file mode 100644
index 0000000..06283cf
Binary files /dev/null and b/icons original/parametre.png differ
diff --git a/icons original/parametre2.png b/icons original/parametre2.png
new file mode 100644
index 0000000..9b5bfca
Binary files /dev/null and b/icons original/parametre2.png differ
diff --git a/icons original/proxmox.png b/icons original/proxmox.png
new file mode 100644
index 0000000..650c0fb
Binary files /dev/null and b/icons original/proxmox.png differ
diff --git a/icons original/selfh-st.png b/icons original/selfh-st.png
new file mode 100644
index 0000000..15fa14c
Binary files /dev/null and b/icons original/selfh-st.png differ
diff --git a/icons original/smb.png b/icons original/smb.png
new file mode 100644
index 0000000..eb940d3
Binary files /dev/null and b/icons original/smb.png differ
diff --git a/icons original/ssh.png b/icons original/ssh.png
new file mode 100644
index 0000000..0b9fca8
Binary files /dev/null and b/icons original/ssh.png differ
diff --git a/icons original/terminal.png b/icons original/terminal.png
new file mode 100644
index 0000000..c719926
Binary files /dev/null and b/icons original/terminal.png differ
diff --git a/icons original/termix.png b/icons original/termix.png
new file mode 100644
index 0000000..b5dd7d3
Binary files /dev/null and b/icons original/termix.png differ
diff --git a/icons original/upsnap.png b/icons original/upsnap.png
new file mode 100644
index 0000000..99fcff1
Binary files /dev/null and b/icons original/upsnap.png differ
diff --git a/icons original/vaultwarden.png b/icons original/vaultwarden.png
new file mode 100644
index 0000000..b0b64ea
Binary files /dev/null and b/icons original/vaultwarden.png differ
diff --git a/icons original/videoplayer.png b/icons original/videoplayer.png
new file mode 100644
index 0000000..ba001d4
Binary files /dev/null and b/icons original/videoplayer.png differ
diff --git a/icons original/vscode.png b/icons original/vscode.png
new file mode 100644
index 0000000..b4bd206
Binary files /dev/null and b/icons original/vscode.png differ
diff --git a/icons original/watchip.png b/icons original/watchip.png
new file mode 100644
index 0000000..66a2015
Binary files /dev/null and b/icons original/watchip.png differ
diff --git a/icons original/youtube.png b/icons original/youtube.png
new file mode 100644
index 0000000..654c3de
Binary files /dev/null and b/icons original/youtube.png differ
diff --git a/icons original/zigbee2mqtt.png b/icons original/zigbee2mqtt.png
new file mode 100644
index 0000000..3eab252
Binary files /dev/null and b/icons original/zigbee2mqtt.png differ
diff --git a/icons original/zigbee2mqtt2.png b/icons original/zigbee2mqtt2.png
new file mode 100644
index 0000000..c343aef
Binary files /dev/null and b/icons original/zigbee2mqtt2.png differ
diff --git a/icons/adguard-home.png b/icons/adguard-home.png
new file mode 100644
index 0000000..a47c528
Binary files /dev/null and b/icons/adguard-home.png differ
diff --git a/icons/aliexpress.png b/icons/aliexpress.png
new file mode 100644
index 0000000..122f5c0
Binary files /dev/null and b/icons/aliexpress.png differ
diff --git a/icons/amazon-prime-video.png b/icons/amazon-prime-video.png
new file mode 100644
index 0000000..1b89084
Binary files /dev/null and b/icons/amazon-prime-video.png differ
diff --git a/icons/amazon.png b/icons/amazon.png
new file mode 100644
index 0000000..be8de41
Binary files /dev/null and b/icons/amazon.png differ
diff --git a/icons/arcane.png b/icons/arcane.png
new file mode 100644
index 0000000..b3b3d25
Binary files /dev/null and b/icons/arcane.png differ
diff --git a/icons/calculatrice.png b/icons/calculatrice.png
new file mode 100644
index 0000000..2101426
Binary files /dev/null and b/icons/calculatrice.png differ
diff --git a/icons/chatgpt.png b/icons/chatgpt.png
new file mode 100644
index 0000000..a910d73
Binary files /dev/null and b/icons/chatgpt.png differ
diff --git a/icons/chrome.png b/icons/chrome.png
new file mode 100644
index 0000000..5840f82
Binary files /dev/null and b/icons/chrome.png differ
diff --git a/icons/claude.png b/icons/claude.png
new file mode 100644
index 0000000..34dd1ab
Binary files /dev/null and b/icons/claude.png differ
diff --git a/icons/color-picker.svg b/icons/color-picker.svg
new file mode 100644
index 0000000..b838e9f
--- /dev/null
+++ b/icons/color-picker.svg
@@ -0,0 +1,6 @@
+
diff --git a/icons/comere43.jpeg b/icons/comere43.jpeg
new file mode 100644
index 0000000..46a5bc4
Binary files /dev/null and b/icons/comere43.jpeg differ
diff --git a/icons/digiposte.png b/icons/digiposte.png
new file mode 100644
index 0000000..0952d52
Binary files /dev/null and b/icons/digiposte.png differ
diff --git a/icons/dokuwiki.png b/icons/dokuwiki.png
new file mode 100644
index 0000000..49311db
Binary files /dev/null and b/icons/dokuwiki.png differ
diff --git a/icons/excalidraw.png b/icons/excalidraw.png
new file mode 100644
index 0000000..16182ff
Binary files /dev/null and b/icons/excalidraw.png differ
diff --git a/icons/fermer.png b/icons/fermer.png
new file mode 100644
index 0000000..41542ed
Binary files /dev/null and b/icons/fermer.png differ
diff --git a/icons/fermer2.png b/icons/fermer2.png
new file mode 100644
index 0000000..750d068
Binary files /dev/null and b/icons/fermer2.png differ
diff --git a/icons/firefox.png b/icons/firefox.png
new file mode 100644
index 0000000..32b2396
Binary files /dev/null and b/icons/firefox.png differ
diff --git a/icons/gdrive.png b/icons/gdrive.png
new file mode 100644
index 0000000..b3efbda
Binary files /dev/null and b/icons/gdrive.png differ
diff --git a/icons/gemini.png b/icons/gemini.png
new file mode 100644
index 0000000..71abfad
Binary files /dev/null and b/icons/gemini.png differ
diff --git a/icons/gitea.png b/icons/gitea.png
new file mode 100644
index 0000000..3fa86c4
Binary files /dev/null and b/icons/gitea.png differ
diff --git a/icons/gmail.png b/icons/gmail.png
new file mode 100644
index 0000000..2754146
Binary files /dev/null and b/icons/gmail.png differ
diff --git a/icons/gnumeric.png b/icons/gnumeric.png
new file mode 100644
index 0000000..88fb4d3
Binary files /dev/null and b/icons/gnumeric.png differ
diff --git a/icons/google.png b/icons/google.png
new file mode 100644
index 0000000..c9e82a7
Binary files /dev/null and b/icons/google.png differ
diff --git a/icons/googlenews.png b/icons/googlenews.png
new file mode 100644
index 0000000..0aba3d3
Binary files /dev/null and b/icons/googlenews.png differ
diff --git a/icons/gparameter.png b/icons/gparameter.png
new file mode 100644
index 0000000..6b299cb
Binary files /dev/null and b/icons/gparameter.png differ
diff --git a/icons/grok.png b/icons/grok.png
new file mode 100644
index 0000000..15f30e8
Binary files /dev/null and b/icons/grok.png differ
diff --git a/icons/hardware_benchtools.png b/icons/hardware_benchtools.png
new file mode 100644
index 0000000..f4f97bf
Binary files /dev/null and b/icons/hardware_benchtools.png differ
diff --git a/icons/heimdall.png b/icons/heimdall.png
new file mode 100644
index 0000000..c943103
Binary files /dev/null and b/icons/heimdall.png differ
diff --git a/icons/home-assistant.png b/icons/home-assistant.png
new file mode 100644
index 0000000..10e36de
Binary files /dev/null and b/icons/home-assistant.png differ
diff --git a/icons/hortusfox.png b/icons/hortusfox.png
new file mode 100644
index 0000000..bed33d9
Binary files /dev/null and b/icons/hortusfox.png differ
diff --git a/icons/immich.png b/icons/immich.png
new file mode 100644
index 0000000..f3b71a5
Binary files /dev/null and b/icons/immich.png differ
diff --git a/icons/ipwatch.png b/icons/ipwatch.png
new file mode 100644
index 0000000..c6a6085
Binary files /dev/null and b/icons/ipwatch.png differ
diff --git a/icons/korben.jpeg b/icons/korben.jpeg
new file mode 100644
index 0000000..74982e8
Binary files /dev/null and b/icons/korben.jpeg differ
diff --git a/icons/memos.png b/icons/memos.png
new file mode 100644
index 0000000..e627934
Binary files /dev/null and b/icons/memos.png differ
diff --git a/icons/mistral.png b/icons/mistral.png
new file mode 100644
index 0000000..c5f3d5a
Binary files /dev/null and b/icons/mistral.png differ
diff --git a/icons/monitor.png b/icons/monitor.png
new file mode 100644
index 0000000..eeeaf8b
Binary files /dev/null and b/icons/monitor.png differ
diff --git a/icons/mqttexplorer.png b/icons/mqttexplorer.png
new file mode 100644
index 0000000..ec7d254
Binary files /dev/null and b/icons/mqttexplorer.png differ
diff --git a/icons/nautilus.png b/icons/nautilus.png
new file mode 100644
index 0000000..8620896
Binary files /dev/null and b/icons/nautilus.png differ
diff --git a/icons/nfs.png b/icons/nfs.png
new file mode 100644
index 0000000..a9cf020
Binary files /dev/null and b/icons/nfs.png differ
diff --git a/icons/nginx-proxy-manager.png b/icons/nginx-proxy-manager.png
new file mode 100644
index 0000000..c13803c
Binary files /dev/null and b/icons/nginx-proxy-manager.png differ
diff --git a/icons/night-day.png b/icons/night-day.png
new file mode 100644
index 0000000..c4514bd
Binary files /dev/null and b/icons/night-day.png differ
diff --git a/icons/night-day2.png b/icons/night-day2.png
new file mode 100644
index 0000000..bca693a
Binary files /dev/null and b/icons/night-day2.png differ
diff --git a/icons/nodered.png b/icons/nodered.png
new file mode 100644
index 0000000..5b77b3c
Binary files /dev/null and b/icons/nodered.png differ
diff --git a/icons/ollama.png b/icons/ollama.png
new file mode 100644
index 0000000..c290f4e
Binary files /dev/null and b/icons/ollama.png differ
diff --git a/icons/orange_tv.png b/icons/orange_tv.png
new file mode 100644
index 0000000..4edcc35
Binary files /dev/null and b/icons/orange_tv.png differ
diff --git a/icons/paperless.png b/icons/paperless.png
new file mode 100644
index 0000000..7aec8ac
Binary files /dev/null and b/icons/paperless.png differ
diff --git a/icons/parameter.png b/icons/parameter.png
new file mode 100644
index 0000000..a10c299
Binary files /dev/null and b/icons/parameter.png differ
diff --git a/icons/parametre.png b/icons/parametre.png
new file mode 100644
index 0000000..0a50107
Binary files /dev/null and b/icons/parametre.png differ
diff --git a/icons/parametre2.png b/icons/parametre2.png
new file mode 100644
index 0000000..b0f38b3
Binary files /dev/null and b/icons/parametre2.png differ
diff --git a/icons/proxmox.png b/icons/proxmox.png
new file mode 100644
index 0000000..dc3da0a
Binary files /dev/null and b/icons/proxmox.png differ
diff --git a/icons/scanservjs.png b/icons/scanservjs.png
new file mode 100644
index 0000000..3d7699f
Binary files /dev/null and b/icons/scanservjs.png differ
diff --git a/icons/selfh-st.png b/icons/selfh-st.png
new file mode 100644
index 0000000..fb26ca3
Binary files /dev/null and b/icons/selfh-st.png differ
diff --git a/icons/smb.png b/icons/smb.png
new file mode 100644
index 0000000..d44c799
Binary files /dev/null and b/icons/smb.png differ
diff --git a/icons/ssh.png b/icons/ssh.png
new file mode 100644
index 0000000..2cf88c0
Binary files /dev/null and b/icons/ssh.png differ
diff --git a/icons/terminal.png b/icons/terminal.png
new file mode 100644
index 0000000..6baa9a3
Binary files /dev/null and b/icons/terminal.png differ
diff --git a/icons/termix.png b/icons/termix.png
new file mode 100644
index 0000000..f93dfa7
Binary files /dev/null and b/icons/termix.png differ
diff --git a/icons/upsnap.png b/icons/upsnap.png
new file mode 100644
index 0000000..a47bd4d
Binary files /dev/null and b/icons/upsnap.png differ
diff --git a/icons/vaultwarden.png b/icons/vaultwarden.png
new file mode 100644
index 0000000..63c9597
Binary files /dev/null and b/icons/vaultwarden.png differ
diff --git a/icons/videoplayer.png b/icons/videoplayer.png
new file mode 100644
index 0000000..ba001d4
Binary files /dev/null and b/icons/videoplayer.png differ
diff --git a/icons/vscode.png b/icons/vscode.png
new file mode 100644
index 0000000..f3b4f08
Binary files /dev/null and b/icons/vscode.png differ
diff --git a/icons/youtube.png b/icons/youtube.png
new file mode 100644
index 0000000..e22fed7
Binary files /dev/null and b/icons/youtube.png differ
diff --git a/icons/zigbee2mqtt.png b/icons/zigbee2mqtt.png
new file mode 100644
index 0000000..b809ba6
Binary files /dev/null and b/icons/zigbee2mqtt.png differ
diff --git a/icons/zigbee2mqtt2.png b/icons/zigbee2mqtt2.png
new file mode 100644
index 0000000..1cb569d
Binary files /dev/null and b/icons/zigbee2mqtt2.png differ
diff --git a/image.png b/image.png
new file mode 100644
index 0000000..78990a7
Binary files /dev/null and b/image.png differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..22f1787
--- /dev/null
+++ b/index.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+ SSH Launcher
+
+
+
+
+
SSH Launcher
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resize_icons.sh b/resize_icons.sh
new file mode 100755
index 0000000..3979744
--- /dev/null
+++ b/resize_icons.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# Dossier contenant les icônes
+ICON_DIR="./icons"
+
+# Vérification
+if [ ! -d "$ICON_DIR" ]; then
+ echo "Dossier '$ICON_DIR' introuvable."
+ exit 1
+fi
+
+# Parcours des PNG
+for img in "$ICON_DIR"/*.png; do
+ [ -e "$img" ] || continue # si aucun fichier
+ echo "Redimensionnement : $img"
+ convert "$img" -resize 64x "$img"
+done
+
+echo "Terminé."
diff --git a/scripts/install.sh b/scripts/install.sh
new file mode 100755
index 0000000..1a6a5c4
--- /dev/null
+++ b/scripts/install.sh
@@ -0,0 +1,135 @@
+#!/bin/bash
+#
+# install.sh - Script d'installation de SSH Launcher
+# Installe l'extension GNOME Shell, l'application GTK et le handler SSH
+#
+
+set -e
+
+# Couleurs pour l'affichage
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Répertoire du projet
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+
+echo -e "${GREEN}=== Installation de SSH Launcher ===${NC}"
+echo "Répertoire du projet: $PROJECT_DIR"
+echo ""
+
+# Vérifier les dépendances
+echo -e "${YELLOW}Vérification des dépendances...${NC}"
+
+check_dependency() {
+ if ! command -v "$1" &> /dev/null; then
+ echo -e "${RED}Erreur: $1 n'est pas installé${NC}"
+ echo "Installez-le avec: $2"
+ exit 1
+ fi
+ echo -e " ✓ $1"
+}
+
+check_python_module() {
+ if ! python3 -c "import $1" 2>/dev/null; then
+ echo -e "${RED}Erreur: module Python '$1' non trouvé${NC}"
+ echo "Installez-le avec: $2"
+ exit 1
+ fi
+ echo -e " ✓ Python module: $1"
+}
+
+check_dependency "python3" "sudo apt install python3"
+check_dependency "gnome-shell" "Installez GNOME Shell"
+
+check_python_module "gi" "sudo apt install python3-gi"
+check_python_module "yaml" "sudo apt install python3-yaml"
+
+# Vérifier GTK3 et WebKit2
+python3 -c "import gi; gi.require_version('Gtk', '3.0')" 2>/dev/null || {
+ echo -e "${RED}Erreur: GTK3 non disponible${NC}"
+ echo "Installez avec: sudo apt install gir1.2-gtk-3.0"
+ exit 1
+}
+echo -e " ✓ GTK3"
+
+python3 -c "import gi; gi.require_version('WebKit2', '4.1')" 2>/dev/null || {
+ echo -e "${RED}Erreur: WebKit2 4.1 non disponible${NC}"
+ echo "Installez avec: sudo apt install gir1.2-webkit2-4.1"
+ exit 1
+}
+echo -e " ✓ WebKit2 4.1"
+
+echo ""
+
+# Rendre les scripts exécutables
+echo -e "${YELLOW}Configuration des permissions...${NC}"
+chmod +x "$PROJECT_DIR/scripts/ssh-url"
+chmod +x "$PROJECT_DIR/app/ssh-launcher-gtk.py"
+echo -e " ✓ Scripts rendus exécutables"
+
+# Mettre à jour les chemins dans les fichiers
+echo -e "${YELLOW}Configuration des chemins...${NC}"
+
+# Mettre à jour extension.js avec le bon chemin
+sed -i "s|const APP_PATH = .*|const APP_PATH = GLib.build_filenamev(['$PROJECT_DIR', 'app', 'ssh-launcher-gtk.py']);|" \
+ "$PROJECT_DIR/extension/extension.js"
+echo -e " ✓ extension.js mis à jour"
+
+# Mettre à jour ssh-url.desktop
+sed -i "s|Exec=.*|Exec=$PROJECT_DIR/scripts/ssh-url %u|" \
+ "$PROJECT_DIR/desktop/ssh-url.desktop"
+echo -e " ✓ ssh-url.desktop mis à jour"
+
+# Mettre à jour ssh-launcher-gtk.desktop
+sed -i "s|Exec=.*|Exec=python3 $PROJECT_DIR/app/ssh-launcher-gtk.py|" \
+ "$PROJECT_DIR/app/ssh-launcher-gtk.desktop"
+echo -e " ✓ ssh-launcher-gtk.desktop mis à jour"
+
+echo ""
+
+# Installer le handler SSH
+echo -e "${YELLOW}Installation du handler SSH...${NC}"
+cp "$PROJECT_DIR/desktop/ssh-url.desktop" ~/.local/share/applications/
+xdg-mime default ssh-url.desktop x-scheme-handler/ssh
+echo -e " ✓ Handler ssh:// installé"
+
+# Installer l'application dans les applications locales
+echo -e "${YELLOW}Installation de l'application...${NC}"
+cp "$PROJECT_DIR/app/ssh-launcher-gtk.desktop" ~/.local/share/applications/
+echo -e " ✓ Application ajoutée au menu"
+
+# Mettre à jour la base de données des applications
+update-desktop-database ~/.local/share/applications/ 2>/dev/null || true
+echo -e " ✓ Base de données mise à jour"
+
+echo ""
+
+# Installer l'extension GNOME Shell
+echo -e "${YELLOW}Installation de l'extension GNOME Shell...${NC}"
+EXTENSION_DIR="$HOME/.local/share/gnome-shell/extensions/ssh-launcher@local"
+
+mkdir -p "$EXTENSION_DIR"
+cp "$PROJECT_DIR/extension/metadata.json" "$EXTENSION_DIR/"
+cp "$PROJECT_DIR/extension/extension.js" "$EXTENSION_DIR/"
+cp "$PROJECT_DIR/extension/stylesheet.css" "$EXTENSION_DIR/"
+echo -e " ✓ Extension copiée dans $EXTENSION_DIR"
+
+echo ""
+echo -e "${GREEN}=== Installation terminée ===${NC}"
+echo ""
+echo -e "${YELLOW}Prochaines étapes :${NC}"
+echo "1. Redémarrez GNOME Shell (Alt+F2, tapez 'r', Entrée) ou déconnectez-vous"
+echo "2. Activez l'extension avec:"
+echo " gnome-extensions enable ssh-launcher@local"
+echo ""
+echo "3. Ou activez via GNOME Extensions / Extension Manager"
+echo ""
+echo -e "${YELLOW}Test immédiat de l'application :${NC}"
+echo " python3 $PROJECT_DIR/app/ssh-launcher-gtk.py"
+echo ""
+echo -e "${YELLOW}Configuration :${NC}"
+echo " Éditez $PROJECT_DIR/config/equipements.yaml"
+echo ""
diff --git a/scripts/ssh-url b/scripts/ssh-url
new file mode 100755
index 0000000..9ae7411
--- /dev/null
+++ b/scripts/ssh-url
@@ -0,0 +1,47 @@
+#!/bin/bash
+#
+# ssh-url - Handler pour les URLs ssh://
+# Reçoit une URL ssh://user@host et ouvre un terminal avec la connexion SSH
+#
+
+# URL reçue en argument (ex: ssh://gilles@10.0.0.24)
+URL="$1"
+
+if [ -z "$URL" ]; then
+ echo "Usage: $0 ssh://user@host"
+ exit 1
+fi
+
+# Extraire la partie user@host en supprimant le préfixe ssh://
+SSH_TARGET="${URL#ssh://}"
+
+# Supprimer un éventuel / final
+SSH_TARGET="${SSH_TARGET%/}"
+
+if [ -z "$SSH_TARGET" ]; then
+ echo "Erreur: impossible d'extraire la cible SSH de l'URL: $URL"
+ exit 1
+fi
+
+# Détecter le terminal disponible et lancer SSH
+# Liste des terminaux supportés (ordre de préférence)
+if command -v gnome-terminal &> /dev/null; then
+ gnome-terminal -- ssh "$SSH_TARGET"
+elif command -v xfce4-terminal &> /dev/null; then
+ xfce4-terminal -e "ssh $SSH_TARGET"
+elif command -v konsole &> /dev/null; then
+ konsole -e ssh "$SSH_TARGET"
+elif command -v xterm &> /dev/null; then
+ xterm -e ssh "$SSH_TARGET"
+elif command -v terminator &> /dev/null; then
+ terminator -e "ssh $SSH_TARGET"
+elif command -v tilix &> /dev/null; then
+ tilix -e "ssh $SSH_TARGET"
+else
+ # Fallback: notification d'erreur si possible
+ if command -v notify-send &> /dev/null; then
+ notify-send "SSH Handler" "Aucun terminal graphique trouvé"
+ fi
+ echo "Erreur: aucun terminal graphique trouvé"
+ exit 1
+fi
diff --git a/upgrade_1.md b/upgrade_1.md
new file mode 100644
index 0000000..8280d0e
--- /dev/null
+++ b/upgrade_1.md
@@ -0,0 +1,189 @@
+# upgrade_1.md
+## Extension GNOME Shell : Icône dans la barre + Popup configurable + YAML
+
+Ce document définit les nouvelles directives pour faire évoluer l’application actuelle vers une intégration complète dans GNOME Shell.
+
+---
+
+# 1. Objectif général
+
+L’application doit désormais inclure **une extension GNOME Shell** qui :
+
+1. Ajoute **une icône dans la barre supérieure** (Top Bar).
+2. Ouvre, au clic sur cette icône, **une fenêtre popup dédiée** affichant un tableau interactif d’équipements et de services.
+3. Génère ce tableau **à partir d’un fichier YAML**, permettant de modifier les équipements sans toucher au code.
+4. Gère l’ouverture d’URL (SSH, HTTP, HTTPS…) associées à chaque service.
+
+Ce fichier décrit également le **processus à suivre pour Claude Code** afin d’analyser, proposer, puis implémenter les fonctionnalités.
+
+---
+
+# 2. Nouvelle architecture fonctionnelle
+
+## 2.1 Extension GNOME Shell : icône dans la barre supérieure
+
+L’extension doit :
+
+- Ajouter une **icône** dans le panel, visible en permanence.
+- Au clic, afficher un **popup** ou un **panneau flottant** contenant la liste des équipements.
+- Le popup doit pouvoir être **fermé**, soit via un second clic sur l’icône, soit par toute action prévue par GNOME Shell.
+
+### Contenu du popup
+
+Le popup devra afficher :
+
+- Une liste d’**équipements** (PC, serveurs, VM…)
+- Pour chaque équipement :
+ - Un label (nom)
+ - Une liste de **services** associés
+ - Pour chaque service :
+ - une icône
+ - un nom
+ - une URL à ouvrir
+
+---
+
+# 3. Fichier de configuration YAML
+
+Créer le fichier :
+
+```
+config/equipements.yaml
+```
+
+Structure attendue :
+
+```yaml
+equipements:
+ - nom: "PC Bureau"
+ ip: "10.0.0.24"
+ services:
+ - nom: "SSH"
+ icon: "terminal"
+ url: "ssh://gilles@10.0.0.24"
+ - nom: "Interface Web"
+ icon: "web-browser"
+ url: "http://10.0.0.24:8080"
+
+ - nom: "Serveur Proxmox"
+ ip: "10.0.0.10"
+ services:
+ - nom: "SSH"
+ icon: "terminal"
+ url: "ssh://root@10.0.0.10"
+ - nom: "Proxmox UI"
+ icon: "server"
+ url: "https://10.0.0.10:8006"
+```
+
+Notes :
+
+- Un équipement peut contenir plusieurs services.
+- Chaque service doit afficher une icône.
+- Tous les champs doivent être extensibles (ajouter plus d’attributs dans le futur si besoin).
+
+---
+
+# 4. Contraintes techniques GNOME Shell
+
+Il est important de tenir compte des restrictions :
+
+- **WebKitGTK est interdite dans les extensions GNOME Shell**.
+ Impossible d’intégrer du vrai HTML directement.
+
+Donc le popup devra être :
+
+- soit un **ensemble de widgets St.*** créés depuis le YAML,
+- soit une **fenêtre externe GTK** (possible via un script ou app séparée).
+
+L’analyse devra tenir compte de ces contraintes.
+
+---
+
+# 5. Analyse obligatoire avant développement (Claude)
+
+Claude doit impérativement produire une **analyse complète** avant toute modification du projet.
+
+## 5.1 Ce que Claude doit analyser
+
+Claude doit étudier plusieurs architectures possibles, notamment :
+
+### **A : Popup GNOME Shell natif (widgets St)**
+- Parsing YAML dans l’extension.
+- Construction d’interfaces entièrement via St.BoxLayout / St.Icon / St.Button.
+
+### **B : Extension + application GTK externe (WebKit)**
+- L’extension déclenche une application GTK qui affiche du vrai HTML.
+
+### **C : Extension GNOME Shell + navigateur en mode app**
+- L’extension ouvre une fenêtre Chromium/Webkit en mode application.
+
+### **D : Serveur local + widgets GNOME Shell générés depuis un JSON/YAML**
+- Génération HTML ou JSON via fichier local.
+
+Pour chaque solution, Claude doit fournir :
+
+- avantages,
+- inconvénients,
+- complexité,
+- compatibilité,
+- performances,
+- ergonomie.
+
+### Il doit produire :
+
+- un **tableau comparatif**,
+- une **recommandation ordonnée**.
+
+**Aucun code ne doit être généré avant mon choix explicite.**
+
+---
+
+# 6. Protocole de travail imposé à Claude Code
+
+### **`UPGRADE:1 ANALYSE`**
+Claude réalise :
+- analyse complète,
+- comparaison des options,
+- pas de code.
+
+### **`UPGRADE:1 CHOIX `**
+Je choisis l’architecture souhaitée.
+
+Claude :
+- confirme l’impact,
+- annonce quels fichiers seront créés ou modifiés,
+- pas encore de code.
+
+### **`UPGRADE:1 APPLY`**
+Claude applique :
+- création et mise à jour des fichiers,
+- ajout de l’extension GNOME Shell,
+- parser YAML,
+- popup dynamique,
+- URLs fonctionnelles.
+
+### **`UPGRADE:1 DOC`**
+Claude met à jour la documentation utilisateur.
+
+---
+
+# 7. Résultat attendu
+
+À la fin de cette évolution :
+
+- Une **icône** apparaît dans la barre GNOME.
+- Un **popup dynamique** s’ouvre avec la liste des équipements.
+- Le contenu du popup provient du fichier **YAML**.
+- Les clics déclenchent l’ouverture des URLs.
+- L’extension est propre, documentée, testable.
+
+---
+
+# 8. Résumé
+
+Ce fichier sert de **référence officielle** pour diriger l’analyse et le développement par Claude Code :
+
+- Analyse → Choix → Implémentation → Documentation.
+- Aucun code avant `UPGRADE:1 APPLY`.
+