feat(boot): écran de chargement inline + staging backend/frontend pour le bundle desktop
CI / lint (push) Failing after 16s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
CI / security (push) Successful in 18s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Failing after 16s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
CI / security (push) Successful in 18s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
- frontend/index.html : splash #boot-splash 100% inline (CSS+JS), scripts CDN en defer pour ne pas bloquer le premier paint - frontend/js/app.js : signale le boot terminé via __OBSIGATE_BOOTED (try/finally) - desktop/src/main.rs : met à jour le statut du splash avant la redirection vers le backend - ci: stage backend/ + frontend/ sous desktop/ avant tauri build (resources backend/** et frontend/**) dans desktop-build.yml et build-linux.sh - desktop/gen/schemas/windows-schema.json : régénéré (permissions store/updater) - docs: README, README.fr, ROADMAP
This commit is contained in:
@@ -29,6 +29,12 @@ jobs:
|
||||
cd desktop/python-embed
|
||||
./python.exe -m pip install --no-cache-dir -r ../../backend/requirements.txt
|
||||
|
||||
- name: Stage backend + frontend for bundle
|
||||
working-directory: desktop
|
||||
run: |
|
||||
xcopy /E /I /Q /Y "..\backend" "backend"
|
||||
xcopy /E /I /Q /Y "..\frontend" "frontend"
|
||||
|
||||
- name: Build MSI
|
||||
working-directory: desktop
|
||||
run: cargo tauri build --bundles msi
|
||||
@@ -68,6 +74,12 @@ jobs:
|
||||
source desktop/python-embed/venv/bin/activate
|
||||
pip install -r backend/requirements.txt
|
||||
|
||||
- name: Stage backend + frontend for bundle
|
||||
working-directory: desktop
|
||||
run: |
|
||||
cp -r ../backend backend
|
||||
cp -r ../frontend frontend
|
||||
|
||||
- name: Build AppImage
|
||||
working-directory: desktop
|
||||
run: cargo tauri build --bundles appimage
|
||||
|
||||
+801
-90
@@ -1,99 +1,810 @@
|
||||
# ObsiGate — Gestionnaire de connaissances personnel
|
||||
# ObsiGate
|
||||
|
||||
**ObsiGate** est une application web moderne de gestion de notes Markdown, conçue pour
|
||||
remplacer des outils comme Obsidian avec une expérience auto-hébergée complète.
|
||||
> **Version française** — ce document est le miroir synchronisé de [README.md](README.md) (référence complète). Dernière synchronisation : juin 2026.
|
||||
|
||||
## ✨ Fonctionnalités principales
|
||||
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
|
||||
|
||||
- **📝 Éditeur Markdown** avancé avec aperçu en temps réel
|
||||
- **🤖 IA intégrée** : DeepSeek, OpenRouter, Gemini — complétion, réécriture, traduction
|
||||
- **🔍 Recherche full-text** avec TF-IDF et filtres avancés
|
||||
- **📊 Dashboard** avec statistiques, fichiers récents, et contenu partagé
|
||||
- **🏷️ Tags** : organisation et filtrage par tags
|
||||
- **🗺️ Vue Graphe** : visualisation des liens entre fichiers
|
||||
- **🔄 Synchronisation** en temps réel via SSE (Server-Sent Events)
|
||||
- **🔗 Webhooks** : notifications automatiques vers des services externes
|
||||
- **💾 Sauvegardes** automatiques et restauration
|
||||
- **🎨 15 thèmes** avec modes clair et sombre
|
||||
- **🌐 Internationalisation** : anglais et français (sélecteur de langue intégré)
|
||||
- **📱 PWA** : installation en tant qu'application native
|
||||
- **🔒 Authentification** JWT avec tokens refresh
|
||||
|
||||
## 🚀 Installation rapide
|
||||
|
||||
### Docker (recommandé)
|
||||
```bash
|
||||
git clone https://git.dracodev.net/Projets/ObsiGate.git
|
||||
cd ObsiGate
|
||||
cp .env.example .env
|
||||
# Configurer DEEPSEEK_API_KEY dans .env
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
### Accès
|
||||
- **Interface web** : http://localhost:2020
|
||||
- **Utilisateur par défaut** : admin / admin (à changer immédiatement)
|
||||
|
||||
## 📋 Prérequis
|
||||
|
||||
- Docker & Docker Compose
|
||||
- Clé API DeepSeek, OpenRouter, ou Gemini (optionnelle, pour l'IA)
|
||||
- Python 3.11+ (pour le développement)
|
||||
|
||||
## 🛠️ Développement
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python backend/main.py
|
||||
# Dans un autre terminal :
|
||||
cd frontend && python -m http.server 8080
|
||||
```
|
||||
|
||||
## 📁 Structure du projet
|
||||
[]()
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://www.docker.com/)
|
||||
[](https://www.python.org/)
|
||||
[](https://git.dracodev.net/Projets/ObsiGate/actions)
|
||||
|
||||
```
|
||||
ObsiGate/
|
||||
backend/ # API FastAPI (Python)
|
||||
frontend/ # Interface utilisateur (HTML/CSS/JS vanilla)
|
||||
locales/ # Traductions (en.json, fr.json)
|
||||
docker/ # Configuration Docker
|
||||
docs/ # Documentation et roadmap
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ [🔍 Recherche...] [☀/🌙 Thème] ObsiGate │
|
||||
├──────────────┬──────────────────────────────────────────┤
|
||||
│ SIDEBAR │ CONTENT AREA │
|
||||
│ ▼ Recettes │ 📄 Titre du fichier │
|
||||
│ 📁 Soupes │ Tags: #recette #rapide │
|
||||
│ 📄 Pizza │ [Contenu Markdown rendu] │
|
||||
│ ▼ IT │ │
|
||||
│ 📁 Docker │ │
|
||||
│ Tags Cloud │ │
|
||||
└──────────────┴──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 🌍 Internationalisation
|
||||
|
||||
ObsiGate supporte l'anglais et le français. Le sélecteur de langue se trouve dans
|
||||
**Paramètres > Langue**. La traduction couvre :
|
||||
|
||||
- ✅ Interface utilisateur complète
|
||||
- ✅ Guide d'utilisation (18 sections)
|
||||
- ✅ Messages système et toasts
|
||||
- ✅ Descriptions des thèmes et commandes
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
- [README.md](README.md) — Documentation complète en anglais
|
||||
- [docs/ROADMAP.md](docs/ROADMAP.md) — Feuille de route du projet
|
||||
- [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) — Guide de contribution
|
||||
- [docs/TROUBLESHOOTING.md](TROUBLESHOOTING.md) — Dépannage
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
Variables d'environnement principales (`.env`) :
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `DEEPSEEK_API_KEY` | Clé API pour l'IA DeepSeek |
|
||||
| `OPENROUTER_API_KEY` | Clé API pour OpenRouter |
|
||||
| `GEMINI_API_KEY` | Clé API pour Gemini |
|
||||
| `ADMIN_PASSWORD` | Mot de passe administrateur |
|
||||
| `JWT_SECRET` | Secret pour les tokens JWT |
|
||||
| `WEBHOOK_SECRET` | Secret pour la signature des webhooks |
|
||||
|
||||
## 📝 Licence
|
||||
|
||||
Ce projet est sous licence propriétaire. Voir le fichier [LICENSE](LICENSE) pour plus de détails.
|
||||
|
||||
---
|
||||
|
||||
**ObsiGate** — Votre savoir, votre serveur. 🏰
|
||||
## 📋 Table des matières
|
||||
|
||||
- [Fonctionnalités](#fonctionnalites)
|
||||
- [Prérequis](#prerequis)
|
||||
- [Installation rapide](#installation-rapide)
|
||||
- [Configuration détaillée](#configuration-detaillee)
|
||||
- [Variables d'environnement](#variables-denvironnement)
|
||||
- [🔒 Authentification](#authentification)
|
||||
- [Ajouter une nouvelle vault](#ajouter-une-nouvelle-vault)
|
||||
- [Build & déploiement avec build.sh](#build-deploiement-avec-buildsh)
|
||||
- [Rendu d'images Obsidian](#rendu-dimages-obsidian)
|
||||
- [Desktop (Tauri) — Application native](#desktop-tauri-application-native)
|
||||
- [Utilisation](#utilisation)
|
||||
- [API](#api)
|
||||
- [Recherche avancée](#recherche-avancee)
|
||||
- [Dépannage](#depannage)
|
||||
- [Performance](#performance)
|
||||
- [Sécurité](#securite)
|
||||
- [Stack technique](#stack-technique)
|
||||
- [Architecture](#architecture)
|
||||
- [Développement](#developpement)
|
||||
- [Licence](#licence)
|
||||
- [Changelog](#changelog)
|
||||
|
||||
---
|
||||
|
||||
## ✨ Fonctionnalités
|
||||
|
||||
- **🤖 AI Editor intégré** — Éditeur CodeMirror 6 avec toolbar IA : amélioration, correction, traduction, génération, réécriture personnalisée, toolbox (liste, tableau, frontmatter, canvas) — multi-provider DeepSeek/OpenRouter/Gemini
|
||||
- **🗺️ Vue graphe interactive** — Canvas force-directed avec Barnes-Hut O(n log n), filtres (tag, type), profondeur, mode focus, historique de navigation ←→↑, export PNG, aperçu au survol (Ctrl+click)
|
||||
- **🗂️ Multi-vault** : Visualisez plusieurs vaults Obsidian simultanément
|
||||
- **🌳 Navigation arborescente** : Parcourez vos dossiers et fichiers dans la sidebar
|
||||
- **🔍 Recherche avancée** : Moteur TF-IDF avec stemming français, normalisation des accents, snippets surlignés, facettes, pagination et tri
|
||||
- **💡 Autocomplétion intelligente** : Suggestions de fichiers, tags et historique avec navigation clavier
|
||||
- **🧩 Syntaxe de requête** : Opérateurs `tag:`, `#`, `vault:`, `title:`, `path:`, `ext:` avec chips visuels
|
||||
- **📜 Historique de recherche** : Persisté en localStorage (max 50 entrées, LIFO, dédupliqué)
|
||||
- **🏷️ Tag cloud** : Filtrage par tags extraits des frontmatters YAML
|
||||
- **🔗 Wikilinks** : Les `[[liens internes]]` Obsidian sont cliquables
|
||||
- **🖼️ Images Obsidian** : Support complet des syntaxes d'images Obsidian avec résolution intelligente
|
||||
- **🎨 Syntax highlight** : Coloration syntaxique des blocs de code
|
||||
- **🌓 Thème clair/sombre** : Toggle persisté en localStorage
|
||||
- **📡 Synchronisation temps réel** : Surveillance automatique des fichiers via watchdog avec mise à jour incrémentale de l'index
|
||||
- **📡 Server-Sent Events** : Notifications SSE pour les changements d'index avec reconnexion automatique
|
||||
- **➕ Gestion dynamique des vaults** : Ajout/suppression de vaults via API sans redémarrage
|
||||
- **🖥️ Application desktop native** : Tauri (Rust) + backend Python embarqué, sans Docker ni navigateur
|
||||
- **🐳 Docker multi-platform** : linux/amd64, linux/arm64, linux/arm/v7, linux/386
|
||||
- **🔒 Authentification** : JWT + Argon2id, sessions persistantes, contrôle d'accès par vault
|
||||
- **🛡️ Sécurité** : Rate limiting, audit log, backup automatique, redaction de secrets, headers CSP, protection path traversal, utilisateur non-root
|
||||
- **⚡ Performance** : Compression GZip, Cache-Control immutable, index inversé incrémental, search sans I/O disque
|
||||
- **❤️ Healthcheck** : Endpoint `/api/health` intégré pour Docker et monitoring
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Prérequis
|
||||
|
||||
### Système requis
|
||||
- **Docker** >= 20.10
|
||||
- **docker-compose** >= 2.0
|
||||
- **Espace disque** : ~200MB pour l'image Docker
|
||||
|
||||
### Systèmes supportés
|
||||
- Linux (Ubuntu, Debian, CentOS, etc.)
|
||||
- macOS (Intel et Apple Silicon)
|
||||
- Windows (avec Docker Desktop)
|
||||
- NAS compatibles Docker (Synology, QNAP, etc.)
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Installation rapide
|
||||
|
||||
### 1. Cloner le dépôt
|
||||
|
||||
```bash
|
||||
git clone https://git.dracodev.net/Projets/ObsiGate.git
|
||||
cd ObsiGate
|
||||
```
|
||||
|
||||
### 2. Configurer vos vaults et vos secrets
|
||||
|
||||
Éditez `docker-compose.yml` pour ajouter vos vaults Obsidian :
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /chemin/absolu/vers/votre/vault:/vaults/NomDeVotreVault:ro
|
||||
```
|
||||
|
||||
> **Important** : Le chemin doit être absolu et le volume en lecture seule (`:ro`)
|
||||
|
||||
Créez votre fichier `.env` pour l'authentification et les secrets :
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Éditez .env pour configurer vos mots de passe et options
|
||||
```
|
||||
|
||||
> **Ne committez jamais `.env` !** Il est dans `.gitignore`. Utilisez `.env.example` comme référence.
|
||||
|
||||
### 3. Lancer l'application
|
||||
|
||||
```bash
|
||||
chmod +x build.sh # une seule fois
|
||||
./build.sh # build + déploiement en une commande
|
||||
```
|
||||
|
||||
> Options utiles : `./build.sh --help`, `./build.sh --cache` (rebuild rapide), `./build.sh --build-only` (construire sans démarrer).
|
||||
|
||||
### 4. Accéder à l'interface
|
||||
|
||||
Ouvrez votre navigateur sur : **http://localhost:2020**
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration détaillée
|
||||
|
||||
### Étape 1 : Préparation des vaults
|
||||
|
||||
1. **Localisez vos vaults Obsidian** sur votre système
|
||||
2. **Notez les chemins absolus** vers chaque dossier
|
||||
3. **Vérifiez les permissions** : Docker doit pouvoir lire ces dossiers
|
||||
|
||||
### Étape 2 : Configuration docker-compose.yml
|
||||
|
||||
```yaml
|
||||
services:
|
||||
obsigate:
|
||||
build:
|
||||
context: .
|
||||
image: obsigate:latest
|
||||
container_name: obsigate
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "2020:8080" # Port local 2020 → Port conteneur 8080
|
||||
volumes:
|
||||
- /home/user/Documents/Obsidian-Recettes:/vaults/Recettes:ro
|
||||
- /home/user/Documents/Obsidian-IT:/vaults/IT:ro
|
||||
- ./data:/app/data # Persistance des données d'auth
|
||||
environment:
|
||||
- VAULT_1_NAME=Recettes
|
||||
- VAULT_1_PATH=/vaults/Recettes
|
||||
- VAULT_2_NAME=IT
|
||||
- VAULT_2_PATH=/vaults/IT
|
||||
- OBSIGATE_AUTH_ENABLED=true
|
||||
- OBSIGATE_ADMIN_USER=admin
|
||||
env_file:
|
||||
- .env # Contient OBSIGATE_ADMIN_PASSWORD et autres secrets
|
||||
```
|
||||
|
||||
### Étape 3 : Build & déploiement
|
||||
|
||||
```bash
|
||||
chmod +x build.sh
|
||||
./build.sh
|
||||
```
|
||||
|
||||
**Alternative manuelle :**
|
||||
|
||||
```bash
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
> **Compatibilité Docker** : l'image utilise une variante minimale d'`uvicorn` et `fastapi 0.110.3` afin d'éviter les dépendances optionnelles natives (`watchfiles`, `uvloop`, …) qui peuvent échouer au build sur Alpine, ARM ou i386.
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Variables d'environnement
|
||||
|
||||
Les vaults sont configurées par paires `VAULT_N_NAME` / `VAULT_N_PATH` (N = 1, 2, 3…) :
|
||||
|
||||
| Variable | Description | Exemple |
|
||||
|----------|-------------|---------|
|
||||
| `VAULT_1_NAME` | Nom affiché de la vault | `Recettes` |
|
||||
| `VAULT_1_PATH` | Chemin dans le conteneur | `/vaults/Obsidian-RECETTES` |
|
||||
| `VAULT_1_ATTACHMENTS_PATH` | Dossier d'attachements (optionnel) | `06_Boite_a_Outils/6.2_Attachments` |
|
||||
| `VAULT_1_SCAN_ATTACHMENTS` | Scan d'images au démarrage (défaut : true) | `true` |
|
||||
|
||||
**Règles de nommage :** lettres, chiffres et tirets uniquement ; pas d'espaces ; le nom doit correspondre au chemin dans le conteneur.
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Authentification
|
||||
|
||||
> **Désactivée par défaut** — Compatible avec toutes les installations existantes.
|
||||
|
||||
Système optionnel basé sur **JWT + Argon2id** avec contrôle d'accès par vault.
|
||||
|
||||
### Activer l'authentification
|
||||
|
||||
1. `cp .env.example .env`
|
||||
2. Éditez `.env` :
|
||||
```bash
|
||||
OBSIGATE_AUTH_ENABLED=true
|
||||
OBSIGATE_ADMIN_USER=admin
|
||||
OBSIGATE_ADMIN_PASSWORD=votre_mot_de_passe # Laissez vide = auto-généré (voir logs)
|
||||
# OBSIGATE_SECURE_COOKIES=false # true si derrière HTTPS
|
||||
```
|
||||
3. Dans `docker-compose.yml` : `env_file: - .env`
|
||||
|
||||
> **Ne mettez jamais de mot de passe dans `docker-compose.yml` !** Utilisez toujours `.env`.
|
||||
|
||||
### Premier démarrage
|
||||
|
||||
Si aucun utilisateur n'existe, ObsiGate crée automatiquement un compte admin et **affiche le mot de passe une seule fois dans les logs** :
|
||||
|
||||
```bash
|
||||
docker-compose logs obsigate | grep -A4 "PREMIER"
|
||||
```
|
||||
|
||||
### Gestion des utilisateurs via CLI
|
||||
|
||||
```bash
|
||||
# Créer un utilisateur
|
||||
docker exec obsigate python backend/create_admin.py create alice MonMotDePasse --role user --vaults Recettes IT
|
||||
|
||||
# Créer un admin avec accès total
|
||||
docker exec obsigate python backend/create_admin.py create bob SecretPass --role admin --vaults "*"
|
||||
|
||||
# Lister / supprimer
|
||||
docker exec obsigate python backend/create_admin.py list
|
||||
docker exec obsigate python backend/create_admin.py delete alice
|
||||
```
|
||||
|
||||
### Interface d'administration
|
||||
|
||||
Un compte **admin** connecté voit une icône 🛡️ dans le header : liste, création/édition/suppression d'utilisateurs, assignation des vaults, activation/désactivation de comptes.
|
||||
|
||||
### Contrôle d'accès par vault
|
||||
|
||||
| Valeur vaults | Accès |
|
||||
|---------------|-------|
|
||||
| `["*"]` | Toutes les vaults (y compris futures) — défaut admin |
|
||||
| `["Recettes", "IT"]` | Uniquement ces vaults |
|
||||
| `[]` | Aucun accès |
|
||||
|
||||
### Variables d'environnement d'auth
|
||||
|
||||
| Variable | Description | Défaut |
|
||||
|----------|-------------|--------|
|
||||
| `OBSIGATE_AUTH_ENABLED` | Activer l'authentification | `false` |
|
||||
| `OBSIGATE_ADMIN_USER` | Nom de l'admin auto-créé | `admin` |
|
||||
| `OBSIGATE_ADMIN_PASSWORD` | Mot de passe admin (vide = auto-généré) | *(auto)* |
|
||||
| `OBSIGATE_SECURE_COOKIES` | Cookie `Secure` (HTTPS uniquement) | `false` |
|
||||
| `OBSIGATE_ACCESS_TOKEN_TTL` | Durée de vie token JWT (secondes) | `3600` |
|
||||
| `OBSIGATE_REFRESH_TOKEN_TTL` | Durée de vie refresh token (secondes) | `2592000` |
|
||||
| `OBSIGATE_LOGIN_MAX_ATTEMPTS` | Tentatives de login max par IP | `10` |
|
||||
| `OBSIGATE_LOGIN_WINDOW_SECONDS` | Fenêtre de rate limiting (secondes) | `900` |
|
||||
|
||||
### Volume pour la persistance
|
||||
|
||||
Les données d'auth (`users.json`, `secret.key`) sont stockées dans `/app/data` :
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./data:/app/data # Persistance des utilisateurs et clé JWT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ➕ Ajouter une nouvelle vault
|
||||
|
||||
### Méthode 1 : Édition directe
|
||||
|
||||
1. `docker-compose down`
|
||||
2. Ajoutez le volume : `- /nouveau/chemin/vault:/vaults/NouvelleVault:ro`
|
||||
3. Ajoutez les variables : `VAULT_4_NAME=NouvelleVault` + `VAULT_4_PATH=/vaults/NouvelleVault`
|
||||
4. Redémarrez : `./build.sh`
|
||||
|
||||
### Méthode 2 : Hot-reload (recommandé)
|
||||
|
||||
1. Ajoutez volume + variables comme ci-dessus
|
||||
2. `./build.sh`
|
||||
3. `curl http://localhost:2020/api/index/reload`
|
||||
|
||||
### Méthode 3 : API dynamique (sans redémarrage)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:2020/api/vaults/add \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "NouvelleVault", "path": "/vaults/NouvelleVault"}'
|
||||
|
||||
curl -X DELETE http://localhost:2020/api/vaults/NouvelleVault
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔨 Build & déploiement avec build.sh
|
||||
|
||||
### Utilisation de base
|
||||
|
||||
```bash
|
||||
chmod +x build.sh # une seule fois
|
||||
./build.sh # build from scratch + démarrage
|
||||
```
|
||||
|
||||
### Options disponibles
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--help`, `-h` | Affiche l'aide complète |
|
||||
| `--build-only` | Construit l'image sans démarrer le conteneur |
|
||||
| `--no-cache` | Rebuild complet sans cache Docker **(défaut)** |
|
||||
| `--cache` | Utilise le cache Docker (plus rapide si peu de changements) |
|
||||
| `--progress=plain` | Sortie verbeuse (recommandé pour le debug) |
|
||||
| `--progress=tty` | Sortie interactive avec barres de progression |
|
||||
|
||||
### Ce que fait le script
|
||||
|
||||
1. **Vérifie** Docker et Docker Compose (versions)
|
||||
2. **Valide** `docker-compose.yml` (présence + syntaxe)
|
||||
3. **Vérifie chaque volume** monté (avertit si la source n'existe pas)
|
||||
4. **Construit** l'image Docker (multi-stage, ~180MB)
|
||||
5. **Démarre** le conteneur (`docker compose up -d`)
|
||||
6. **Affiche** statut + logs en temps réel
|
||||
|
||||
### Arrêter / redémarrer
|
||||
|
||||
```bash
|
||||
docker compose down # Arrêter
|
||||
docker compose up -d # Redémarrer sans rebuild
|
||||
docker compose logs -f # Voir les logs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖼️ Rendu d'images Obsidian
|
||||
|
||||
ObsiGate supporte **toutes les syntaxes d'images Obsidian** avec résolution intelligente multi-stratégies.
|
||||
|
||||
### Syntaxes supportées
|
||||
|
||||
1. **Markdown standard avec attributs HTML** : `[<img width="180" src="path/to/image.svg"/>](https://example.com)`
|
||||
2. **Wiki-link embed chemin complet** : `![[06_Boite_a_Outils/6.2_Attachments/image.svg]]`
|
||||
3. **Wiki-link embed nom de fichier** : `![[image.svg]]`
|
||||
4. **Markdown standard** : ``
|
||||
|
||||
### Résolution intelligente (7 stratégies, par priorité)
|
||||
|
||||
1. Chemin absolu
|
||||
2. Dossier d'attachements configuré (`VAULT_N_ATTACHMENTS_PATH`)
|
||||
3. Index de démarrage (match unique)
|
||||
4. Même répertoire que le fichier markdown
|
||||
5. Racine du vault
|
||||
6. Index de démarrage (match le plus proche)
|
||||
7. Fallback : placeholder stylisé `[image not found: filename.ext]`
|
||||
|
||||
### Configuration
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- VAULT_1_NAME=MonVault
|
||||
- VAULT_1_PATH=/vaults/MonVault
|
||||
- VAULT_1_ATTACHMENTS_PATH=Assets/Images # Chemin relatif
|
||||
- VAULT_1_SCAN_ATTACHMENTS=true # Activer le scan (défaut)
|
||||
```
|
||||
|
||||
### Rescan manuel
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:2020/api/attachments/rescan/MonVault
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Desktop (Tauri) — Application native
|
||||
|
||||
ObsiGate Desktop est une application native construite avec [Tauri](https://tauri.app/) (Rust + webview système). Elle embarque le backend Python et le frontend dans un exécutable standalone — zéro Docker, zéro ligne de commande.
|
||||
|
||||
> 🚧 **Version 2.0.0 — binaires en cours de stabilisation.** Pour l'instant, le build depuis les sources est recommandé.
|
||||
|
||||
### Fonctionnalités desktop natives
|
||||
|
||||
| Fonctionnalité | Web | Desktop |
|
||||
|---|---|---|
|
||||
| Accès fichiers local | Via upload | Natif (sélecteur dossier) |
|
||||
| Thème système | Manuel | Auto (suit OS dark/light) |
|
||||
| Notifications | Service Worker | Natif OS |
|
||||
| Associations `.md` | ❌ | ✅ « Ouvrir avec ObsiGate » |
|
||||
| Tray icon | ❌ | ✅ Barre des tâches |
|
||||
| Auto-update | ❌ | ✅ Vérifie les releases Gitea |
|
||||
| Mode hors-ligne | Limité | Complet (backend local) |
|
||||
|
||||
### Téléchargement (binaires pré-buildés)
|
||||
|
||||
Les releases sont publiées sur [Gitea](https://git.dracodev.net/Projets/ObsiGate/releases) :
|
||||
|
||||
| Plateforme | Format |
|
||||
|---|---|
|
||||
| **Linux** | `.deb` + `.AppImage` |
|
||||
| **Windows** | `.msi` + `.exe` (NSIS) |
|
||||
|
||||
```bash
|
||||
# Linux — .deb (Debian/Ubuntu/Deepin)
|
||||
sudo dpkg -i obsigate_2.0.0_amd64.deb
|
||||
# Linux — .AppImage (toute distrib)
|
||||
chmod +x ObsiGate_2.0.0_amd64.AppImage && ./ObsiGate_2.0.0_amd64.AppImage
|
||||
```
|
||||
|
||||
```cmd
|
||||
REM Windows : double-cliquer sur ObsiGate_2.0.0_x64.msi (ou le setup NSIS)
|
||||
```
|
||||
|
||||
### Démarrage
|
||||
|
||||
1. **Lancez l'application** depuis le menu ou la ligne de commande
|
||||
2. Le backend Python démarre automatiquement sur `127.0.0.1:17890` (splash « Démarrage… » pendant le boot)
|
||||
3. La fenêtre s'ouvre et charge l'interface ObsiGate
|
||||
4. **Premier lancement** : sélectionnez le dossier de vos vaults Obsidian via le sélecteur natif
|
||||
5. Pour fermer : icône tray → Quitter (arrêt propre du backend)
|
||||
|
||||
### Build depuis les sources
|
||||
|
||||
Guide détaillé : [desktop/README.md](./desktop/README.md).
|
||||
|
||||
#### Prérequis communs
|
||||
|
||||
| Outil | Version | Installation |
|
||||
|---|---|---|
|
||||
| Rust (cargo) | ≥ 1.75 | `rustup` |
|
||||
| Tauri CLI | ≥ 2.0 | `cargo install tauri-cli` |
|
||||
| Git | — | — |
|
||||
| Dépendances système Linux | — | `sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev` |
|
||||
|
||||
> **Important — staging :** `tauri.conf.json` embarque `backend/**` et `frontend/**` **depuis le dossier `desktop/`**.
|
||||
> Les scripts de build copient automatiquement `../backend` et `../frontend` dans `desktop/` avant `cargo tauri build`.
|
||||
> Sans ce staging, le build échoue avec « glob pattern backend/**/* path not found ».
|
||||
|
||||
#### 🪟 Windows — `build-windows.bat`
|
||||
|
||||
```cmd
|
||||
REM Prérequis (via Scoop) : rustup, curl, git
|
||||
scoop install rustup curl git
|
||||
rustup default stable
|
||||
cargo install tauri-cli
|
||||
|
||||
cd desktop
|
||||
build-windows.bat
|
||||
```
|
||||
|
||||
Étapes du script :
|
||||
|
||||
1. Tue les processus Python résiduels (`taskkill /F /IM python.exe`)
|
||||
2. Télécharge **Python 3.11 embed** (python.org) → `desktop\python-embed\` + activation de pip (`python311._pth`)
|
||||
3. `pip install -r ..\backend\requirements.txt` dans l'embed
|
||||
4. **Staging** : copie `..\backend` et `..\frontend` dans `desktop\`
|
||||
5. `cargo tauri build --target x86_64-pc-windows-msvc --bundles nsis`
|
||||
6. Copie `python-embed` à côté de l'exécutable (`target\x86_64-pc-windows-msvc\release\`) pour le mode dev local
|
||||
7. Nettoie les dossiers stagés
|
||||
|
||||
→ **Artefact :** `desktop\target\x86_64-pc-windows-msvc\release\bundle\nsis\ObsiGate_2.0.0_x64-setup.exe`
|
||||
|
||||
#### 🐧 Linux — `build-linux.sh`
|
||||
|
||||
```bash
|
||||
cd desktop
|
||||
chmod +x build-linux.sh
|
||||
./build-linux.sh
|
||||
```
|
||||
|
||||
Étapes du script :
|
||||
|
||||
1. Vérifie Rust + Tauri CLI, installe les dépendances système (apt)
|
||||
2. Crée un venv Python `desktop/python-embed/venv` + `pip install -r ../backend/requirements.txt`
|
||||
3. **Staging** : copie `../backend` et `../frontend` dans `desktop/`
|
||||
4. `cargo tauri build --target x86_64-unknown-linux-gnu --bundles deb,appimage`
|
||||
5. Copie le runtime (`python-embed/`, `backend/`, `frontend/`) à côté de l'exécutable
|
||||
|
||||
→ **Artefacts :**
|
||||
|
||||
- `desktop/target/x86_64-unknown-linux-gnu/release/bundle/deb/obsigate_2.0.0_amd64.deb`
|
||||
- `desktop/target/x86_64-unknown-linux-gnu/release/bundle/appimage/ObsiGate_2.0.0_amd64.AppImage`
|
||||
|
||||
### 🤖 Builds CI/CD — artefacts automatiques
|
||||
|
||||
**Oui** — le workflow [`.gitea/workflows/desktop-build.yml`](./.gitea/workflows/desktop-build.yml) construit les binaires desktop à chaque push sur `main` touchant `desktop/**`, `frontend/**` ou `backend/**` (et manuellement via `workflow_dispatch`), sur des **runners self-hosted** :
|
||||
|
||||
| Job | Runner | Artefacts (conservés 30 jours) |
|
||||
|---|---|---|
|
||||
| `build-windows` | `[self-hosted, windows, desktop]` | `desktop/target/release/bundle/msi/*.msi` |
|
||||
| `build-linux` | `[self-hosted, linux, desktop]` | `*.AppImage` + `*.deb` |
|
||||
|
||||
- Les artefacts sont téléchargeables depuis la page **Actions** du run Gitea.
|
||||
- La publication en **Gitea Release** est prévue sur les tags `v*` (étape `Publish to Gitea Release`).
|
||||
- Le workflow web [`.gitea/workflows/ci.yml`](./.gitea/workflows/ci.yml) gère de son côté lint → tests → sécurité → build Docker → e2e Playwright.
|
||||
|
||||
### Architecture Desktop
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Tauri (Rust) │
|
||||
│ ├─ Webview (webview système) │
|
||||
│ │ └─ Frontend (HTML/JS/CSS) │
|
||||
│ └─ Sidecar Python │
|
||||
│ └─ uvicorn backend.main:app │
|
||||
│ └─ port 127.0.0.1:17890 │
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Cycle de vie : Tauri spawn le backend Python → health check → splash de démarrage → redirection vers la webview. À la fermeture : kill propre du backend.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Utilisation
|
||||
|
||||
### Interface web
|
||||
|
||||
1. **Navigation** : Cliquez sur les vaults dans la sidebar pour les développer
|
||||
2. **Recherche** : Utilisez la barre de recherche pour chercher dans toutes les vaults
|
||||
3. **Tags** : Cliquez sur les tags pour filtrer les contenus
|
||||
4. **Wikilinks** : Les liens `[[page]]` sont cliquables et navigables
|
||||
5. **Images** : Toutes les syntaxes d'images Obsidian sont rendues automatiquement
|
||||
6. **Thème** : Basculez entre thème clair/sombre avec l'icône 🌙/☀️
|
||||
|
||||
### Raccourcis clavier
|
||||
|
||||
| Action | Raccourci |
|
||||
|--------|-----------|
|
||||
| Recherche | `Ctrl + K` ou `/` |
|
||||
| Toggle thème | `Ctrl + T` |
|
||||
| Focus recherche | `Esc` |
|
||||
|
||||
---
|
||||
|
||||
## 🔌 API
|
||||
|
||||
ObsiGate expose une API REST complète :
|
||||
|
||||
| Endpoint | Description | Méthode | Auth |
|
||||
|----------|-------------|---------|------|
|
||||
| `/api/health` | Health check (status, version, stats) | GET | Non |
|
||||
| `/api/auth/status` | Statut auth (activé, utilisateurs présents) | GET | Non |
|
||||
| `/api/auth/login` | Connexion (access token + cookie refresh) | POST | Non |
|
||||
| `/api/auth/refresh` | Renouveler l'access token via cookie refresh | POST | Cookie |
|
||||
| `/api/auth/logout` | Déconnexion + révocation refresh token | POST | Oui |
|
||||
| `/api/auth/me` | Infos utilisateur courant | GET | Oui |
|
||||
| `/api/auth/change-password` | Changer son mot de passe | POST | Oui |
|
||||
| `/api/auth/admin/users` | Lister / créer des utilisateurs | GET/POST | Admin |
|
||||
| `/api/auth/admin/users/{u}` | Modifier / supprimer un utilisateur | PATCH/DELETE | Admin |
|
||||
| `/api/vaults` | Liste des vaults (filtrée par permissions) | GET | Oui |
|
||||
| `/api/browse/{vault}?path=` | Navigation dans les dossiers | GET | Oui |
|
||||
| `/api/file/{vault}?path=` | Contenu rendu d'un fichier | GET | Oui |
|
||||
| `/api/file/{vault}/raw?path=` | Contenu brut d'un fichier | GET | Oui |
|
||||
| `/api/file/{vault}/download?path=` | Téléchargement d'un fichier | GET | Oui |
|
||||
| `/api/file/{vault}/save?path=` | Sauvegarder un fichier | PUT | Oui |
|
||||
| `/api/file/{vault}?path=` | Supprimer un fichier | DELETE | Oui |
|
||||
| `/api/search/advanced` | Recherche avancée TF-IDF | GET | Oui |
|
||||
| `/api/suggest` / `/api/tags/suggest` | Autocomplétion | GET | Oui |
|
||||
| `/api/tags?vault=` | Tags uniques avec compteurs | GET | Oui |
|
||||
| `/api/index/reload` | Force un re-scan des vaults | GET | Admin |
|
||||
| `/api/events` | Flux SSE temps réel | GET | Oui |
|
||||
| `/api/vaults/add` / `/api/vaults/{name}` | Gestion dynamique des vaults | POST/DELETE | Admin |
|
||||
| `/api/image/{vault}?path=` | Servir une image | GET | Oui |
|
||||
| `/api/config` | Lire / écrire la configuration | GET/POST | Oui/Admin |
|
||||
| `/api/diagnostics` | Statistiques index et mémoire | GET | Admin |
|
||||
|
||||
> Quand `OBSIGATE_AUTH_ENABLED=false`, tous les endpoints sont accessibles sans token.
|
||||
> Tous les endpoints exposent des schémas Pydantic documentés ; doc interactive sur `/docs` (Swagger UI).
|
||||
|
||||
**Exemples :**
|
||||
|
||||
```bash
|
||||
curl http://localhost:2020/api/health
|
||||
curl http://localhost:2020/api/vaults
|
||||
curl "http://localhost:2020/api/search/advanced?q=recette%20tag:cuisine&vault=all&limit=20&offset=0&sort=relevance"
|
||||
curl "http://localhost:2020/api/suggest?q=piz&vault=all"
|
||||
curl "http://localhost:2020/api/file/Recettes?path=pizza.md"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Recherche avancée
|
||||
|
||||
### Syntaxe de requête
|
||||
|
||||
| Opérateur | Description | Exemple |
|
||||
|-----------|-------------|---------|
|
||||
| `tag:<nom>` | Filtrer par tag | `tag:recette docker` |
|
||||
| `#<nom>` | Raccourci tag | `#linux serveur` |
|
||||
| `vault:<nom>` | Filtrer par vault | `vault:IT kubernetes` |
|
||||
| `title:<texte>` | Filtrer par titre | `title:pizza` |
|
||||
| `path:<texte>` | Filtrer par chemin | `path:recettes/soupes` |
|
||||
| `ext:<type>` | Filtrer par type de fichier | `ext:md kubernetes` |
|
||||
| `"phrase exacte"` | Recherche de phrase | `tag:"multi mots"` |
|
||||
|
||||
Les opérateurs sont combinables : `tag:linux vault:IT ext:md serveur web`.
|
||||
|
||||
### Raccourcis clavier
|
||||
|
||||
| Raccourci | Action |
|
||||
|-----------|--------|
|
||||
| `Ctrl+K` / `Cmd+K` | Focaliser la barre de recherche |
|
||||
| `/` | Focaliser la recherche (hors champ texte) |
|
||||
| `↑` / `↓` | Naviguer dans les suggestions |
|
||||
| `Enter` | Sélectionner la suggestion active ou lancer la recherche |
|
||||
| `Escape` | Fermer les suggestions / quitter la recherche |
|
||||
|
||||
### Fonctionnalités
|
||||
|
||||
- **TF-IDF** : scoring par fréquence pondérée des termes
|
||||
- **Boost titre** : correspondances dans le titre ×3
|
||||
- **Normalisation des accents** : `resume` trouve `résumé`
|
||||
- **Snippets surlignés** (`<mark>`), **facettes** (compteurs par vault/tag), **pagination** (50/page), **tri** pertinence/date, **chips** de filtres, **historique** (50 recherches)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Dépannage
|
||||
|
||||
**Port déjà utilisé :**
|
||||
|
||||
```bash
|
||||
sudo netstat -tulpn | grep 2020
|
||||
# Changer le port dans docker-compose.yml : ports: - "2021:8080"
|
||||
```
|
||||
|
||||
**Vault non trouvée :** chemins absolus, permissions de lecture, redémarrer le conteneur après modification.
|
||||
|
||||
**Build échoue :**
|
||||
|
||||
```bash
|
||||
docker system prune -f
|
||||
docker compose down
|
||||
./build.sh --progress=plain
|
||||
# Si l'échec persiste :
|
||||
./build.sh --progress=plain 2>&1 | tee build.log
|
||||
```
|
||||
|
||||
**Logs pour debugging :**
|
||||
|
||||
```bash
|
||||
docker compose logs -f obsigate
|
||||
docker compose logs --tail=100 obsigate
|
||||
```
|
||||
|
||||
**Desktop :** les logs du backend sont dans `%APPDATA%\ObsiGate\logs\backend.log` (Windows) / `~/.config/obsigate/logs/backend.log` (Linux).
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Performance
|
||||
|
||||
| Métrique | Estimation |
|
||||
|----------|------------|
|
||||
| **Indexation** | ~1–2s pour 1 000 fichiers markdown |
|
||||
| **Recherche avancée** | < 10ms pour la plupart des requêtes (index inversé + TF-IDF) |
|
||||
| **Résolution wikilinks** | O(1) via table de lookup |
|
||||
| **Mémoire** | ~80–150MB par 1 000 fichiers (contenu capé à 100 KB/fichier) |
|
||||
| **Image Docker** | ~180MB (multi-stage) |
|
||||
| **CPU** | Non-bloquant ; recherche offloadée sur thread pool dédié |
|
||||
|
||||
### Paramètres recommandés par taille de vault
|
||||
|
||||
| Taille | Fichiers | `search_workers` | `prefix_max_expansions` | `max_content_size` |
|
||||
|--------|----------|-------------------|--------------------------|---------------------|
|
||||
| Petit | < 500 | 1 | 50 | 100 000 |
|
||||
| Moyen | 500–5 000 | 2 | 50 | 100 000 |
|
||||
| Grand | 5 000+ | 4 | 30 | 50 000 |
|
||||
|
||||
Configurables via l'interface (Settings) ou l'API `/api/config`.
|
||||
|
||||
### Optimisations clés
|
||||
|
||||
- **Index inversé avec set-intersection** + prefix matching par recherche binaire
|
||||
- **ThreadPoolExecutor** : recherche CPU-bound hors de l'event loop asyncio
|
||||
- **InvertedIndex incrémental** : hooks `add_document`/`remove_document`, plus de rebuild O(N)
|
||||
- **Compression GZip** (~70% de bande passante économisée) + **Cache-Control immutable** (1 an)
|
||||
- **Race condition guard** (`currentSearchId` + AbortController), progress bar, timeout 30s
|
||||
- **Rendu Markdown singleton**, debounced icon rendering, recherche sans I/O disque
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Sécurité
|
||||
|
||||
- **Path traversal** : tous les endpoints fichier valident que le chemin résolu reste dans la vault
|
||||
- **Rate limiting** : 10 tentatives de login max par IP sur 15 minutes + lockout par compte (5 tentatives)
|
||||
- **Audit log** : écritures/suppressions/config journalisées dans `data/audit.log` (JSON lines, rotation 10 MB)
|
||||
- **Backup automatique** : chaque modification/suppression sauvegardée dans `.obsigate-backup/` avec timestamp
|
||||
- **Secret redaction** : masquage automatique des JWT, clés API, tokens dans les aperçus
|
||||
- **Utilisateur non-root** : conteneur Docker sous `obsigate` (UID 1000)
|
||||
- **Volumes read-only** : vaults montées en `:ro` par défaut
|
||||
- **Secrets dans `.env`** : jamais dans `docker-compose.yml`
|
||||
- **Atomic writes** : tmp+replace pour users.json, shares.json, webhooks.json
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Stack technique
|
||||
|
||||
- **Backend** : Python 3.11 + FastAPI 0.110 + Uvicorn
|
||||
- **Auth** : python-jose (JWT HS256) + argon2-cffi (Argon2id)
|
||||
- **File Watcher** : watchdog 4.x (inotify natif + fallback polling)
|
||||
- **Frontend** : Vanilla JS + HTML + CSS (zéro framework, zéro build)
|
||||
- **Rendu Markdown** : mistune 3.x
|
||||
- **PDF Export** : WeasyPrint 60+
|
||||
- **Desktop** : Tauri v2 (Rust) + Python embarqué
|
||||
- **Image Docker** : python:3.11-slim (multi-stage)
|
||||
- **Stockage utilisateurs** : JSON local (`data/users.json`) — aucune base de données
|
||||
- **Architecture** : SPA + API REST + SSE
|
||||
|
||||
---
|
||||
|
||||
## 🏠 Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────────────────────────────┐
|
||||
│ Navigateur │◄───►│ FastAPI (backend/main.py) │
|
||||
│ (SPA) │ REST │ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ │ │ indexer.py │ │ search.py │ │
|
||||
│ app.js │ │ │ (scan+cache) │ │ (in-memory) │ │
|
||||
│ style.css │ │ └───────┬────── └──────┬───────┘ │
|
||||
│ index.html │ │ │ │ │
|
||||
└─────────────────┘ │ ┌───┴──────────────┴───┐ │
|
||||
│ │ Index en mémoire │ │
|
||||
│ │ (fichiers, tags, │ │
|
||||
│ │ contenu, lookup) │ │
|
||||
│ └───────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Flux de données :**
|
||||
|
||||
1. Au démarrage, `indexer.py` scanne tous les vaults en parallèle (thread pool, non-bloquant)
|
||||
2. Contenu, tags (YAML + inline) et métadonnées mis en cache en mémoire
|
||||
3. Table de lookup O(1) pour la résolution des wikilinks
|
||||
4. `watcher.py` surveille les fichiers (watchdog natif ou polling)
|
||||
5. Modifications → mise à jour incrémentale de l'index
|
||||
6. Changements notifiés au frontend via SSE
|
||||
7. Recherche sur l'index mémoire (zéro I/O disque)
|
||||
8. Le frontend SPA communique via REST + SSE
|
||||
|
||||
---
|
||||
|
||||
## 📝 Développement
|
||||
|
||||
### Structure du projet
|
||||
|
||||
```
|
||||
ObsiGate/
|
||||
├── backend/ # API FastAPI
|
||||
│ ├── main.py # Endpoints, Pydantic models, rendu markdown
|
||||
│ ├── indexer.py # Scan des vaults, index en mémoire, lookup table
|
||||
│ ├── search.py # Moteur de recherche fulltext avec scoring
|
||||
│ ├── watcher.py # Surveillance fichiers (watchdog + debounce)
|
||||
│ ├── auth/ # Module d'authentification (JWT + Argon2id)
|
||||
│ └── create_admin.py # CLI gestion utilisateurs
|
||||
├── frontend/ # Interface web (Vanilla JS, zéro framework)
|
||||
│ ├── index.html # Page SPA + écran de login + splash de boot
|
||||
│ ├── js/ # Modules ES (app, viewer, editor, graph, …)
|
||||
│ └── style.css # Styles (CSS variables, thèmes, responsive)
|
||||
├── desktop/ # Application native Tauri (Rust + Python embarqué)
|
||||
│ ├── src/main.rs # Shell natif : cycle de vie backend, tray, menus
|
||||
│ ├── build-windows.bat
|
||||
│ └── build-linux.sh
|
||||
├── data/ # Données persistantes (créé au démarrage)
|
||||
├── Dockerfile # Multi-stage, healthcheck, non-root
|
||||
├── docker-compose.yml # Déploiement avec healthcheck et auth env vars
|
||||
├── build.sh # Build & déploiement automatisé
|
||||
└── docs/ # ROADMAP, guides, audits
|
||||
```
|
||||
|
||||
### Contribuer
|
||||
|
||||
Voir [docs/CONTRIBUTING.md](./docs/CONTRIBUTING.md) pour les détails.
|
||||
|
||||
---
|
||||
|
||||
## 📄 Licence
|
||||
|
||||
Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour les détails.
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Support
|
||||
|
||||
- **Issues** : [git.dracodev.net/Projets/ObsiGate/issues](https://git.dracodev.net/Projets/ObsiGate/issues)
|
||||
- **Documentation** : [git.dracodev.net/Projets/ObsiGate/wiki](https://git.dracodev.net/Projets/ObsiGate/wiki)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Changelog
|
||||
|
||||
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.0.0-dev).
|
||||
|
||||
---
|
||||
|
||||
*Projet : ObsiGate | Version : 2.0.0-dev | Dernière mise à jour : Juin 2026*
|
||||
|
||||
@@ -552,28 +552,79 @@ chmod +x ObsiGate_2.0.0_amd64.AppImage
|
||||
|
||||
### Build depuis les sources
|
||||
|
||||
Voir le [guide détaillé dans desktop/](./desktop/README.md).
|
||||
Guide détaillé : [desktop/README.md](./desktop/README.md).
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cd desktop
|
||||
chmod +x build-linux.sh
|
||||
./build-linux.sh
|
||||
# → target/release/bundle/deb/obsigate_2.0.0_amd64.deb
|
||||
# → target/release/bundle/appimage/ObsiGate_2.0.0_amd64.AppImage
|
||||
```
|
||||
#### Prérequis communs
|
||||
|
||||
| Outil | Version | Installation |
|
||||
|---|---|---|
|
||||
| Rust (cargo) | ≥ 1.75 | `rustup` |
|
||||
| Tauri CLI | ≥ 2.0 | `cargo install tauri-cli` |
|
||||
| Git | — | — |
|
||||
| Dépendances système Linux | — | `sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev` |
|
||||
|
||||
> **Important — staging :** `tauri.conf.json` embarque `backend/**` et `frontend/**` **depuis le dossier `desktop/`**.
|
||||
> Les scripts de build copient automatiquement `../backend` et `../frontend` dans `desktop/` avant `cargo tauri build`.
|
||||
> Sans ce staging, le build échoue avec « glob pattern backend/**/* path not found ».
|
||||
|
||||
#### 🪟 Windows — `build-windows.bat`
|
||||
|
||||
```cmd
|
||||
REM Windows (via Scoop)
|
||||
REM Prérequis (via Scoop) : rustup, curl, git
|
||||
scoop install rustup curl git
|
||||
rustup default stable
|
||||
cargo install tauri-cli
|
||||
|
||||
cd desktop
|
||||
build-windows.bat
|
||||
REM → target\release\bundle\msi\ObsiGate_2.0.0_x64.msi
|
||||
```
|
||||
|
||||
Étapes du script :
|
||||
|
||||
1. Tue les processus Python résiduels (`taskkill /F /IM python.exe`)
|
||||
2. Télécharge **Python 3.11 embed** (python.org) → `desktop\python-embed\` + activation de pip (`python311._pth`)
|
||||
3. `pip install -r ..\backend\requirements.txt` dans l'embed
|
||||
4. **Staging** : copie `..\backend` et `..\frontend` dans `desktop\`
|
||||
5. `cargo tauri build --target x86_64-pc-windows-msvc --bundles nsis`
|
||||
6. Copie `python-embed` à côté de l'exécutable (`target\x86_64-pc-windows-msvc\release\`) pour le mode dev local
|
||||
7. Nettoie les dossiers stagés
|
||||
|
||||
→ **Artefact :** `desktop\target\x86_64-pc-windows-msvc\release\bundle\nsis\ObsiGate_2.0.0_x64-setup.exe`
|
||||
|
||||
#### 🐧 Linux — `build-linux.sh`
|
||||
|
||||
```bash
|
||||
cd desktop
|
||||
chmod +x build-linux.sh
|
||||
./build-linux.sh
|
||||
```
|
||||
|
||||
Étapes du script :
|
||||
|
||||
1. Vérifie Rust + Tauri CLI, installe les dépendances système (apt)
|
||||
2. Crée un venv Python `desktop/python-embed/venv` + `pip install -r ../backend/requirements.txt`
|
||||
3. **Staging** : copie `../backend` et `../frontend` dans `desktop/`
|
||||
4. `cargo tauri build --target x86_64-unknown-linux-gnu --bundles deb,appimage`
|
||||
5. Copie le runtime (`python-embed/`, `backend/`, `frontend/`) à côté de l'exécutable
|
||||
|
||||
→ **Artefacts :**
|
||||
|
||||
- `desktop/target/x86_64-unknown-linux-gnu/release/bundle/deb/obsigate_2.0.0_amd64.deb`
|
||||
- `desktop/target/x86_64-unknown-linux-gnu/release/bundle/appimage/ObsiGate_2.0.0_amd64.AppImage`
|
||||
|
||||
### 🤖 Builds CI/CD — artefacts automatiques
|
||||
|
||||
**Oui** — le workflow [`.gitea/workflows/desktop-build.yml`](./.gitea/workflows/desktop-build.yml) construit les binaires desktop à chaque push sur `main` touchant `desktop/**`, `frontend/**` ou `backend/**` (et manuellement via `workflow_dispatch`), sur des **runners self-hosted** :
|
||||
|
||||
| Job | Runner | Artefacts (conservés 30 jours) |
|
||||
|---|---|---|
|
||||
| `build-windows` | `[self-hosted, windows, desktop]` | `desktop/target/release/bundle/msi/*.msi` |
|
||||
| `build-linux` | `[self-hosted, linux, desktop]` | `*.AppImage` + `*.deb` |
|
||||
|
||||
- Les artefacts sont téléchargeables depuis la page **Actions** du run Gitea.
|
||||
- La publication en **Gitea Release** est prévue sur les tags `v*` (étape `Publish to Gitea Release`).
|
||||
- Le workflow web [`.gitea/workflows/ci.yml`](./.gitea/workflows/ci.yml) gère de son côté lint → tests → sécurité → build Docker → e2e Playwright.
|
||||
|
||||
### Architecture Desktop
|
||||
|
||||
```
|
||||
|
||||
@@ -29,6 +29,15 @@ pip install --upgrade pip -q
|
||||
pip install -r ../backend/requirements.txt -q
|
||||
echo "✅ Python environment ready"
|
||||
|
||||
# ── 2b. Stage backend + frontend pour le bundle ───────────────
|
||||
# tauri.conf.json embarque backend/** et frontend/** depuis desktop/ :
|
||||
# sans ce staging, tauri_build échoue (« glob pattern backend/**/* path not found »).
|
||||
echo "[2b/5] Staging backend + frontend for bundle..."
|
||||
rm -rf backend frontend
|
||||
cp -r ../backend backend
|
||||
cp -r ../frontend frontend
|
||||
echo "✅ Staged"
|
||||
|
||||
# ── 3. Build Tauri ────────────────────────────────────────────
|
||||
echo "[3/5] Building Tauri application..."
|
||||
cargo tauri build --target x86_64-unknown-linux-gnu --bundles deb,appimage
|
||||
@@ -43,6 +52,10 @@ cp -r ../backend "$OUTDIR/"
|
||||
cp -r ../frontend "$OUTDIR/"
|
||||
echo "✅ Runtime + frontend copied"
|
||||
|
||||
# ── 4b. Nettoyer les dossiers stagés ──────────────────────────
|
||||
rm -rf backend frontend
|
||||
echo "✅ Cleaned"
|
||||
|
||||
# ── 5. Résultats ───────────────────────────────────────────────
|
||||
OUTDIR="target/x86_64-unknown-linux-gnu/release"
|
||||
echo "[5/5] Results:"
|
||||
|
||||
@@ -6337,6 +6337,234 @@
|
||||
"type": "string",
|
||||
"const": "shell:deny-stdin-write",
|
||||
"markdownDescription": "Denies the stdin_write command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-load`\n- `allow-get-store`\n- `allow-set`\n- `allow-get`\n- `allow-has`\n- `allow-delete`\n- `allow-clear`\n- `allow-reset`\n- `allow-keys`\n- `allow-values`\n- `allow-entries`\n- `allow-length`\n- `allow-reload`\n- `allow-save`",
|
||||
"type": "string",
|
||||
"const": "store:default",
|
||||
"markdownDescription": "This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-load`\n- `allow-get-store`\n- `allow-set`\n- `allow-get`\n- `allow-has`\n- `allow-delete`\n- `allow-clear`\n- `allow-reset`\n- `allow-keys`\n- `allow-values`\n- `allow-entries`\n- `allow-length`\n- `allow-reload`\n- `allow-save`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the clear command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-clear",
|
||||
"markdownDescription": "Enables the clear command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the delete command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-delete",
|
||||
"markdownDescription": "Enables the delete command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the entries command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-entries",
|
||||
"markdownDescription": "Enables the entries command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-get",
|
||||
"markdownDescription": "Enables the get command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-get-store",
|
||||
"markdownDescription": "Enables the get_store command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the has command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-has",
|
||||
"markdownDescription": "Enables the has command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the keys command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-keys",
|
||||
"markdownDescription": "Enables the keys command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the length command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-length",
|
||||
"markdownDescription": "Enables the length command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the load command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-load",
|
||||
"markdownDescription": "Enables the load command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the reload command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-reload",
|
||||
"markdownDescription": "Enables the reload command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the reset command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-reset",
|
||||
"markdownDescription": "Enables the reset command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the save command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-save",
|
||||
"markdownDescription": "Enables the save command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the set command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-set",
|
||||
"markdownDescription": "Enables the set command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the values command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:allow-values",
|
||||
"markdownDescription": "Enables the values command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the clear command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-clear",
|
||||
"markdownDescription": "Denies the clear command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the delete command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-delete",
|
||||
"markdownDescription": "Denies the delete command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the entries command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-entries",
|
||||
"markdownDescription": "Denies the entries command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-get",
|
||||
"markdownDescription": "Denies the get command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-get-store",
|
||||
"markdownDescription": "Denies the get_store command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the has command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-has",
|
||||
"markdownDescription": "Denies the has command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the keys command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-keys",
|
||||
"markdownDescription": "Denies the keys command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the length command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-length",
|
||||
"markdownDescription": "Denies the length command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the load command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-load",
|
||||
"markdownDescription": "Denies the load command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the reload command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-reload",
|
||||
"markdownDescription": "Denies the reload command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the reset command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-reset",
|
||||
"markdownDescription": "Denies the reset command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the save command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-save",
|
||||
"markdownDescription": "Denies the save command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the set command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-set",
|
||||
"markdownDescription": "Denies the set command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the values command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "store:deny-values",
|
||||
"markdownDescription": "Denies the values command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
|
||||
"type": "string",
|
||||
"const": "updater:default",
|
||||
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-check",
|
||||
"markdownDescription": "Enables the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download",
|
||||
"markdownDescription": "Enables the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download-and-install",
|
||||
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-install",
|
||||
"markdownDescription": "Enables the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-check",
|
||||
"markdownDescription": "Denies the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download",
|
||||
"markdownDescription": "Denies the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download-and-install",
|
||||
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-install",
|
||||
"markdownDescription": "Denies the install command without any pre-configured scope."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -566,6 +566,10 @@ fn main() {
|
||||
match wait_for_backend().await {
|
||||
Ok(()) => {
|
||||
if let Some(window) = handle.get_webview_window("main") {
|
||||
// Met à jour le splash inline avant la redirection
|
||||
let _ = window.eval(
|
||||
r#"if (window.__setBootStatus) window.__setBootStatus("Backend prêt — chargement de l'interface…")"#,
|
||||
);
|
||||
match window.eval("window.location.href = 'http://127.0.0.1:17890'") {
|
||||
Ok(_) => info!("Redirect to backend OK"),
|
||||
Err(e) => error!("Redirect eval failed: {}", e),
|
||||
|
||||
+1
-1
@@ -841,7 +841,7 @@
|
||||
- [ ] **Page de release** : intégrer le build desktop dans les releases Gitea + README d'installation
|
||||
|
||||
##### E. Expérience utilisateur (1 jour)
|
||||
- [ ] Écran de chargement pendant le démarrage du backend (« ObsiGate démarre... » avec spinner)
|
||||
- [x] Écran de chargement pendant le démarrage du backend (« ObsiGate démarre... » avec spinner) — splash inline `#boot-splash` dans `index.html`, retiré quand `app.js` signale le boot ; statut mis à jour depuis Rust
|
||||
- [ ] Gestion des erreurs : backend crash → message explicite + bouton « Redémarrer »
|
||||
- [ ] Sauvegarde des préférences desktop (taille fenêtre, position, dernier vault)
|
||||
- [ ] Première expérience : wizard « Choisissez votre vault » au premier lancement
|
||||
|
||||
+69
-3
@@ -47,6 +47,33 @@
|
||||
href="/static/icons/icon-192x192.svg"
|
||||
/>
|
||||
|
||||
<!-- Boot splash CSS — inline so it applies BEFORE /static/style.css is
|
||||
available (desktop: embedded page served from tauri:// has no /static) -->
|
||||
<style id="boot-splash-css">
|
||||
body:not(.app-booted) > *:not(#boot-splash) { display: none !important; }
|
||||
#boot-splash {
|
||||
position: fixed; inset: 0; z-index: 2147483647;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: radial-gradient(1200px 600px at 50% -10%, #1b2030 0%, #0e1117 60%);
|
||||
color: #e6e8ee; font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
body.app-booted #boot-splash { display: none; }
|
||||
.bs-card { display: flex; flex-direction: column; align-items: center; gap: 14px; }
|
||||
.bs-logo {
|
||||
width: 72px; height: 72px; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 38px; background: #171c26; border: 1px solid #2a3242; border-radius: 18px;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
.bs-title { font-size: 22px; font-weight: 600; letter-spacing: 0.3px; }
|
||||
.bs-spinner {
|
||||
width: 34px; height: 34px; border-radius: 50%;
|
||||
border: 3px solid rgba(124, 58, 237, 0.25); border-top-color: #7c3aed;
|
||||
animation: bs-spin 0.9s linear infinite;
|
||||
}
|
||||
@keyframes bs-spin { to { transform: rotate(360deg); } }
|
||||
.bs-status { font-size: 13px; color: #9aa3b2; min-height: 18px; }
|
||||
@media (prefers-reduced-motion: reduce) { .bs-spinner { animation-duration: 1.8s; } }
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
@@ -59,9 +86,10 @@
|
||||
id="hljs-theme-light"
|
||||
disabled
|
||||
/>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://unpkg.com/[email protected]/dist/umd/lucide.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
||||
<!-- defer: ne bloque pas le premier paint (le splash inline s'affiche immédiatement) -->
|
||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script defer src="https://unpkg.com/[email protected]/dist/umd/lucide.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
@@ -172,6 +200,44 @@
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Boot splash : visible tant que l'app n'a pas booté avec son CSS.
|
||||
Sur desktop, la page embarquée (tauri://) n'a pas /static/style.css →
|
||||
sans ce splash, le squelette HTML brut serait visible pendant le
|
||||
démarrage du backend (FOUC 3-10 s). Styles 100% inline. -->
|
||||
<div id="boot-splash" role="status" aria-live="polite">
|
||||
<div class="bs-card">
|
||||
<div class="bs-logo">📖</div>
|
||||
<div class="bs-title">ObsiGate</div>
|
||||
<div class="bs-spinner" aria-hidden="true"></div>
|
||||
<div class="bs-status" id="boot-splash-status">Démarrage…</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var done = false;
|
||||
function bootReady() {
|
||||
if (done) return;
|
||||
done = true;
|
||||
document.body.classList.add("app-booted");
|
||||
var s = document.getElementById("boot-splash");
|
||||
if (s && s.parentNode) s.parentNode.removeChild(s);
|
||||
}
|
||||
window.__obsigateBootReady = bootReady;
|
||||
window.__setBootStatus = function (txt) {
|
||||
var el = document.getElementById("boot-splash-status");
|
||||
if (el) el.textContent = txt;
|
||||
};
|
||||
var poll = setInterval(function () {
|
||||
if (window.__OBSIGATE_BOOTED) { clearInterval(poll); bootReady(); }
|
||||
}, 80);
|
||||
// Filet de sécurité : si le CSS principal a chargé (origine backend réelle)
|
||||
// mais que le JS a échoué, révéler l'app après 10 s au lieu d'un splash infini.
|
||||
setTimeout(function () {
|
||||
var link = document.querySelector('link[href*="style.css"]');
|
||||
if (link && link.sheet) { clearInterval(poll); bootReady(); }
|
||||
}, 10000);
|
||||
})();
|
||||
</script>
|
||||
<div class="search-progress-bar" id="search-progress-bar">
|
||||
<div class="search-progress-bar__fill"></div>
|
||||
</div>
|
||||
|
||||
+9
-2
@@ -102,8 +102,15 @@ async function init() {
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
init();
|
||||
Sync.init();
|
||||
try {
|
||||
init();
|
||||
Sync.init();
|
||||
} finally {
|
||||
// Signale au splash de démarrage (index.html #boot-splash) que l'app est
|
||||
// initialisée : le splash est retiré et le squelette stylé révélé.
|
||||
window.__OBSIGATE_BOOTED = true;
|
||||
if (typeof window.__obsigateBootReady === "function") window.__obsigateBootReady();
|
||||
}
|
||||
// Show version in header
|
||||
fetch('/api/health')
|
||||
.then(r => r.json())
|
||||
|
||||
Reference in New Issue
Block a user