3
172
.$dashboard.drawio.bkp
Normal file
BIN
Capture d’écran du 2025-11-23 14-41-36.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
Capture d’écran du 2025-11-23 14-41-53.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Capture d’écran du 2025-11-23 14-42-03.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
236
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.
|
||||||
|
|||||||
BIN
app/__pycache__/ssh-launcher-gtk.cpython-313.pyc
Normal file
9
app/ssh-launcher-gtk.desktop
Normal file
@@ -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;
|
||||||
764
app/ssh-launcher-gtk.py
Executable file
@@ -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'<span class="service-icon" style="font-size:{size}px">🔗</span>'
|
||||||
|
|
||||||
|
# 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'<img src="file://{icon_path}" class="service-img" style="width:{size}px;height:{size}px" alt="">'
|
||||||
|
|
||||||
|
# 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'<span class="service-icon" style="font-size:{size}px">{emoji}</span>'
|
||||||
|
|
||||||
|
|
||||||
|
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'''
|
||||||
|
<a href="{service.get('url', '')}" class="item" title="{nom} - {service.get('nom', '')}">
|
||||||
|
{icon_html}
|
||||||
|
<span class="label">{service.get('nom', '')}</span>
|
||||||
|
</a>'''
|
||||||
|
distant_html += f'''
|
||||||
|
<div class="ip-box">
|
||||||
|
<div class="ip-header"><span class="status-dot {status_class}" data-ip="{ip}"></span>{nom}</div>
|
||||||
|
<div class="ip-name">{ip}</div>
|
||||||
|
<div class="items">{services_html}</div>
|
||||||
|
</div>'''
|
||||||
|
|
||||||
|
# Section LOCAL
|
||||||
|
local_html = ""
|
||||||
|
for app in local:
|
||||||
|
icon_html = get_icon_html(app.get('icon', ''), icon_local)
|
||||||
|
label_html = f'<span class="label">{app.get("nom", "")}</span>' if show_local_labels else ''
|
||||||
|
local_html += f'''
|
||||||
|
<a href="app://run/{app.get('command', '')}" class="item item-local" title="{app.get('nom', '')}">
|
||||||
|
{icon_html}
|
||||||
|
{label_html}
|
||||||
|
</a>'''
|
||||||
|
|
||||||
|
minitools = config.get('minitools', [])
|
||||||
|
minitools_html = ""
|
||||||
|
for tool in minitools:
|
||||||
|
icon_html = get_icon_html(tool.get('icon', ''), icon_minitools)
|
||||||
|
label_html = f'<span class="label">{tool.get("nom", "")}</span>' 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'''
|
||||||
|
<a href="{href}" class="item mini-item" title="{tool.get('nom', '')}">
|
||||||
|
{icon_html}
|
||||||
|
{label_html}
|
||||||
|
</a>'''
|
||||||
|
|
||||||
|
# Section URL
|
||||||
|
url_html = ""
|
||||||
|
for bookmark in urls:
|
||||||
|
icon_html = get_icon_html(bookmark.get('icon', ''), icon_url)
|
||||||
|
url_html += f'''
|
||||||
|
<a href="{bookmark.get('url', '')}" class="item item-url" title="{bookmark.get('nom', '')}">
|
||||||
|
{icon_html}
|
||||||
|
<span class="label">{bookmark.get('nom', '')}</span>
|
||||||
|
</a>'''
|
||||||
|
|
||||||
|
# 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'''<!DOCTYPE html>
|
||||||
|
<html><head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
|
||||||
|
html, body {{ height: 100%; overflow: hidden; background: transparent; }}
|
||||||
|
body {{
|
||||||
|
font-family: 'Cantarell', 'Segoe UI', sans-serif;
|
||||||
|
font-size: {font_size}px;
|
||||||
|
font-weight: 400;
|
||||||
|
background: transparent;
|
||||||
|
color: {c['text']};
|
||||||
|
}}
|
||||||
|
.container {{
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: {c['container']};
|
||||||
|
border-radius: {border_radius}px;
|
||||||
|
border: 1px solid {c['border']};
|
||||||
|
overflow: hidden;
|
||||||
|
}}
|
||||||
|
/* Header avec LOCAL intégré */
|
||||||
|
.header {{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 8px 14px;
|
||||||
|
background: {c['header']};
|
||||||
|
min-height: 80px;
|
||||||
|
gap: 12px;
|
||||||
|
}}
|
||||||
|
.header-local {{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: {espacement_local}px;
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: {c['box_local']};
|
||||||
|
border-radius: {border_radius_local}px;
|
||||||
|
}}
|
||||||
|
.header-controls {{
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}}
|
||||||
|
.header-btn {{
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}}
|
||||||
|
.header-btn:hover {{
|
||||||
|
background: {c['hover']};
|
||||||
|
}}
|
||||||
|
.header-btn.btn-parametre img {{
|
||||||
|
width: {icon_parametre}px;
|
||||||
|
height: {icon_parametre}px;
|
||||||
|
object-fit: contain;
|
||||||
|
}}
|
||||||
|
.header-btn.btn-fermer img {{
|
||||||
|
width: {icon_fermer}px;
|
||||||
|
height: {icon_fermer}px;
|
||||||
|
object-fit: contain;
|
||||||
|
}}
|
||||||
|
.theme-btn {{
|
||||||
|
overflow: hidden;
|
||||||
|
width: {icon_theme_width}px;
|
||||||
|
height: {icon_theme_height}px;
|
||||||
|
padding: 0;
|
||||||
|
}}
|
||||||
|
.theme-btn img {{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}}
|
||||||
|
.main {{
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}}
|
||||||
|
/* DISTANT à gauche - flex wrap pour ajuster la taille des boxes */
|
||||||
|
.section-distant {{
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: {espacement_distant}px;
|
||||||
|
align-content: start;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 6px;
|
||||||
|
}}
|
||||||
|
/* URL à droite */
|
||||||
|
.section-url {{
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat({col_url}, 1fr);
|
||||||
|
gap: {espacement_url}px;
|
||||||
|
padding: 12px;
|
||||||
|
background: {c['box_url']};
|
||||||
|
border-radius: 10px;
|
||||||
|
align-content: start;
|
||||||
|
min-width: 180px;
|
||||||
|
}}
|
||||||
|
/* Barre de mini outils */
|
||||||
|
.section-minitools {{
|
||||||
|
display: flex;
|
||||||
|
gap: {espacement_minitools}px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: {c['box_minitools']};
|
||||||
|
border-top: 1px solid {c['border']};
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
overflow-x: auto;
|
||||||
|
}}
|
||||||
|
.section-minitools .mini-item {{
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 6px 10px;
|
||||||
|
min-width: 90px;
|
||||||
|
}}
|
||||||
|
.section-minitools .mini-item .label {{
|
||||||
|
font-size: {font_size - 2}px;
|
||||||
|
}}
|
||||||
|
/* IP box - taille ajustée au contenu */
|
||||||
|
.ip-box {{
|
||||||
|
background: {c['box_distant']};
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}}
|
||||||
|
.ip-header {{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: {font_size}px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: {c['text']};
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}}
|
||||||
|
.status-dot {{
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}}
|
||||||
|
.status-up {{
|
||||||
|
background: #2ec27e;
|
||||||
|
}}
|
||||||
|
.status-down {{
|
||||||
|
background: #e01b24;
|
||||||
|
}}
|
||||||
|
.status-unknown {{
|
||||||
|
background: #888888;
|
||||||
|
}}
|
||||||
|
.ip-name {{
|
||||||
|
font-size: {font_size - 2}px;
|
||||||
|
font-family: 'JetBrains Mono', 'Consolas', monospace;
|
||||||
|
color: {c['text2']};
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-left: 14px;
|
||||||
|
}}
|
||||||
|
.items {{
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: {espacement_distant}px;
|
||||||
|
}}
|
||||||
|
.item {{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
background: {c['item']};
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: {c['text']};
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, transform 0.1s;
|
||||||
|
}}
|
||||||
|
.item:hover {{
|
||||||
|
background: {c['hover']};
|
||||||
|
transform: translateY(-2px);
|
||||||
|
color: #fff;
|
||||||
|
}}
|
||||||
|
.item-local {{
|
||||||
|
background: transparent;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}}
|
||||||
|
.item-local:hover {{
|
||||||
|
background: {c['item']};
|
||||||
|
}}
|
||||||
|
.item-url {{
|
||||||
|
background: transparent;
|
||||||
|
padding: 10px;
|
||||||
|
}}
|
||||||
|
.item-url:hover {{
|
||||||
|
background: {c['item']};
|
||||||
|
}}
|
||||||
|
.service-icon {{
|
||||||
|
line-height: 1;
|
||||||
|
}}
|
||||||
|
.service-img {{
|
||||||
|
object-fit: contain;
|
||||||
|
}}
|
||||||
|
.label {{
|
||||||
|
font-size: {font_size - 2}px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: center;
|
||||||
|
color: {c['text2']};
|
||||||
|
max-width: 70px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}}
|
||||||
|
.item:hover .label {{
|
||||||
|
color: #fff;
|
||||||
|
}}
|
||||||
|
/* Scrollbar style */
|
||||||
|
.section-distant::-webkit-scrollbar {{
|
||||||
|
width: 6px;
|
||||||
|
}}
|
||||||
|
.section-distant::-webkit-scrollbar-track {{
|
||||||
|
background: transparent;
|
||||||
|
}}
|
||||||
|
.section-distant::-webkit-scrollbar-thumb {{
|
||||||
|
background: {c['border']};
|
||||||
|
border-radius: 3px;
|
||||||
|
}}
|
||||||
|
.section-distant::-webkit-scrollbar-thumb:hover {{
|
||||||
|
background: {c['text2']};
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-local">{local_html}</div>
|
||||||
|
<div class="header-controls">
|
||||||
|
<button class="header-btn theme-btn" onclick="location.href='app://toggle-theme'" title="Thème">
|
||||||
|
<img src="file://{ICONS_DIR}/night-day.png" alt="">
|
||||||
|
</button>
|
||||||
|
<button class="header-btn btn-parametre" onclick="location.href='app://settings'" title="Paramètres">
|
||||||
|
<img src="file://{ICONS_DIR}/parametre.png" alt="">
|
||||||
|
</button>
|
||||||
|
<button class="header-btn btn-fermer" onclick="location.href='app://close'" title="Fermer">
|
||||||
|
<img src="file://{ICONS_DIR}/fermer.png" alt="">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main">
|
||||||
|
<div class="section-distant">{distant_html}</div>
|
||||||
|
<div class="section-url">{url_html}</div>
|
||||||
|
</div>
|
||||||
|
<div class="section-minitools">{minitools_html}</div>
|
||||||
|
</div>
|
||||||
|
</body></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()
|
||||||
45
app/tools/color-picker/DEVELOPMENT_PROMPT.md
Normal file
@@ -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`.
|
||||||
22
app/tools/color-picker/README.md
Normal file
@@ -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"`).
|
||||||
53
app/tools/color-picker/color_picker.py
Executable file
@@ -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()
|
||||||
47
app/tools/color-picker/palettes/default.json
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
39
app/tools/color-picker/run_color_picker.py
Executable file
@@ -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()
|
||||||
159
app/tools/color-picker/src/app.js
Normal file
@@ -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 = `
|
||||||
|
<header>
|
||||||
|
<h3>${palette.name}</h3>
|
||||||
|
<small>${palette.description}</small>
|
||||||
|
</header>
|
||||||
|
<div class="palette-colors"></div>
|
||||||
|
<button data-palette="${palette.name}">Copier la sélection</button>
|
||||||
|
`;
|
||||||
|
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);
|
||||||
72
app/tools/color-picker/src/index.html
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Color Picker</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app-shell">
|
||||||
|
<aside class="theme-panel" id="theme-panel">
|
||||||
|
<header class="theme-header">
|
||||||
|
<button id="theme-toggle" aria-label="Afficher/masquer le volet">☰</button>
|
||||||
|
<div>
|
||||||
|
<p>Theme CSS</p>
|
||||||
|
<select id="theme-selector"></select>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="theme-preview">
|
||||||
|
<p id="theme-description">Chargement des thèmes...</p>
|
||||||
|
<pre id="theme-preview-content"></pre>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<div class="workspace">
|
||||||
|
<header class="workspace-header">
|
||||||
|
<div>
|
||||||
|
<h1>Color Picker</h1>
|
||||||
|
<p>Roue chromatique, palettes et CSS rapide.</p>
|
||||||
|
</div>
|
||||||
|
<div class="window-actions">
|
||||||
|
<button aria-label="Réduire">−</button>
|
||||||
|
<button aria-label="Agrandir">□</button>
|
||||||
|
<button aria-label="Fermer">✕</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<section class="color-panel">
|
||||||
|
<div class="color-wheel" aria-hidden="true"></div>
|
||||||
|
<div class="color-info">
|
||||||
|
<p class="section-label">Couleur active</p>
|
||||||
|
<input type="color" id="color-input" value="#f94144">
|
||||||
|
<div class="color-swatch-row">
|
||||||
|
<div class="color-box large" id="selected-color"></div>
|
||||||
|
<div class="color-box large" id="complement-color"></div>
|
||||||
|
</div>
|
||||||
|
<div class="color-metadata">
|
||||||
|
<div>
|
||||||
|
<span>HEX</span>
|
||||||
|
<strong id="selected-hex">#f94144</strong>
|
||||||
|
</div>
|
||||||
|
<button data-copy="selected">Copier</button>
|
||||||
|
</div>
|
||||||
|
<div class="color-metadata">
|
||||||
|
<div>
|
||||||
|
<span>Complémentaire</span>
|
||||||
|
<strong id="complement-hex">#0b5ebb</strong>
|
||||||
|
</div>
|
||||||
|
<button data-copy="complement">Copier</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="palette-section">
|
||||||
|
<header>
|
||||||
|
<h2>Palettes web</h2>
|
||||||
|
<p>Chaque palette contient un ensemble de couleurs recommandées et leur complément.</p>
|
||||||
|
</header>
|
||||||
|
<div id="palette-container" class="palette-grid"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
294
app/tools/color-picker/src/style.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/tools/color-picker/themes/monokai.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
13
app/tools/color-picker/themes/solarized-light.css
Normal file
@@ -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);
|
||||||
|
}
|
||||||
16
app/tools/color-picker/themes/themes.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
106
claude_prompt_ssh_app.md
Normal file
@@ -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.
|
||||||
|
|
||||||
330
config/equipements.yaml
Normal file
@@ -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"
|
||||||
296
config/equipements.yaml.yml
Normal file
@@ -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/"
|
||||||
178
dashboard.drawio
Normal file
9
desktop/ssh-url.desktop
Normal file
@@ -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;
|
||||||
111
extension/extension.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
extension/metadata.json
Normal file
@@ -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": ""
|
||||||
|
}
|
||||||
10
extension/stylesheet.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
BIN
icons original/amazon-prime-video.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
icons original/amazon.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
icons original/arcane.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
icons original/calculatrice.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
icons original/chatgpt.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
icons original/chrome.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
icons original/claude.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
icons original/digiposte.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
icons original/dokuwiki.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
icons original/excalidraw.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
icons original/fermer.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
icons original/fermer2.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
icons original/firefox.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
icons original/gemini.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
icons original/gitea.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
icons original/gmail.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
icons original/gnumeric.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
icons original/google.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
icons original/googlenews.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
icons original/gparameter.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
icons original/grok.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
icons original/hardware_benchtools.png
Normal file
|
After Width: | Height: | Size: 980 KiB |
BIN
icons original/heimdall.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
icons original/home-assistant.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
icons original/hortusfox.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
icons original/immich.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
icons original/ipwatch.png
Normal file
|
After Width: | Height: | Size: 703 KiB |
BIN
icons original/memos.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
icons original/monitor.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
icons original/mqttexplorer.png
Normal file
|
After Width: | Height: | Size: 893 KiB |
BIN
icons original/nautilus.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
icons original/nfs.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
icons original/nginx-proxy-manager.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
icons original/night-day.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
icons original/night-day2.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
icons original/nodered.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
icons original/ollama.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
icons original/paperless.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
icons original/parameter.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
icons original/parametre.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
icons original/parametre2.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
icons original/proxmox.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
icons original/selfh-st.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
icons original/smb.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
icons original/ssh.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
icons original/terminal.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
icons original/termix.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
icons original/upsnap.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
icons original/vaultwarden.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
icons original/videoplayer.png
Normal file
|
After Width: | Height: | Size: 826 KiB |
BIN
icons original/vscode.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
icons original/watchip.png
Normal file
|
After Width: | Height: | Size: 751 KiB |
BIN
icons original/youtube.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
icons original/zigbee2mqtt.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
icons original/zigbee2mqtt2.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/adguard-home.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
icons/aliexpress.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
icons/amazon-prime-video.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
icons/amazon.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
icons/arcane.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
icons/calculatrice.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
icons/chatgpt.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
icons/chrome.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
icons/claude.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
6
icons/color-picker.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="30" fill="none" stroke="#f94144" stroke-width="4" />
|
||||||
|
<circle cx="32" cy="32" r="18" stroke="#f9c74f" stroke-width="4" fill="none" />
|
||||||
|
<path d="M32 12 A20 20 0 0 1 52 32 L32 32 Z" fill="#f3722c" />
|
||||||
|
<path d="M32 12 A20 20 0 0 0 12 32 L32 32 Z" fill="#43aa8b" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 385 B |
BIN
icons/comere43.jpeg
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
icons/digiposte.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
icons/dokuwiki.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
icons/excalidraw.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
icons/fermer.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
icons/fermer2.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
icons/firefox.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
icons/gdrive.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |