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

- 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:
2026-08-24 15:50:49 -04:00
parent 6da7fa6786
commit 331c614330
9 changed files with 1199 additions and 107 deletions
+12
View File
@@ -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
View File
@@ -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
[![Version](https://img.shields.io/badge/Version-2.0.0--dev-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
[![CI/CD](https://img.shields.io/badge/CI%2FCD-Gitea_Actions-green.svg)](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** : `![alt text](path/to/image.png)`
### 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*
+62 -11
View File
@@ -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
```
+13
View File
@@ -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:"
+228
View File
@@ -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."
}
]
},
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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())