00
feat: initialisation du site de documentation officiel Dimdam's Docs
63 fichiers modifiés · +4795 −0
Ajouté.env.example+10−0
| @@ -0,0 +1,10 @@ | ||
| 1 | +# Dimdam's Docs. Every value is required: there is no default in the code. Read at build time | |
| 2 | +# (the site is static: rebuild after a change). | |
| 3 | + | |
| 4 | +# Public address of the documentation (canonical links, sitemap) | |
| 5 | +PUBLIC_URL="https://docs.dimdams.com" | |
| 6 | +# Main site (header button) and the panel (API base address, links to the dashboard) | |
| 7 | +MAIN_SITE_URL="https://www.dimdams.com" | |
| 8 | +# Dimdam's Git and Dimdam's Status (links and examples) | |
| 9 | +GIT_URL="https://git.dimdams.com" | |
| 10 | +STATUS_URL="https://status.dimdams.com" | |
Ajouté.gitattributes+3−0
| @@ -0,0 +1,3 @@ | ||
| 1 | +* text=auto eol=lf | |
| 2 | +*.png binary | |
| 3 | +*.webp binary | |
Ajouté.gitignore+11−0
| @@ -0,0 +1,11 @@ | ||
| 1 | +node_modules/ | |
| 2 | +.next/ | |
| 3 | +out/ | |
| 4 | +*.log | |
| 5 | +*.tsbuildinfo | |
| 6 | +next-env.d.ts | |
| 7 | + | |
| 8 | +# Settings: never in the repository. | |
| 9 | +.env | |
| 10 | +.env.* | |
| 11 | +!.env.example | |
AjoutéREADME.md+32−0
| @@ -0,0 +1,32 @@ | ||
| 1 | +# Dimdam's Docs | |
| 2 | + | |
| 3 | +Site de documentation officiel de Dimdam's ([docs.dimdams.com](https://docs.dimdams.com)), remplaçant l'ancien GitBook. | |
| 4 | + | |
| 5 | +Il regroupe toute la documentation des produits Dimdam's en 4 sections : | |
| 6 | +- **Hébergement** (`content/hebergement/`) : Commander, configurer et gérer sites web, bots, serveurs de jeu, VPS et bases de données. | |
| 7 | +- **API & CLI** (`content/api/`) : Piloter les services via l'API REST et le CLI `dimdams`. | |
| 8 | +- **Dimdam's Git** (`content/git/`) : Hébergement Git en France (code, CI/CD, paquets, Pages). | |
| 9 | +- **Dimdam's Status** (`content/status/`) : Suivi de la disponibilité et API de statut. | |
| 10 | + | |
| 11 | +## Architecture technique | |
| 12 | + | |
| 13 | +- **Framework :** Next.js 16 (App Router) avec export 100% statique (`output: "export"`, `trailingSlash: true`). | |
| 14 | +- **Markdown :** Moteur Shiki pour la coloration syntaxique et Marked pour le rendu HTML. | |
| 15 | +- **Recherche :** Index de recherche généré statiquement à la compilation (`/search-index.json`). | |
| 16 | +- **Hébergement :** Fichiers statiques (`out/`) servis directement par Nginx sur `server-003` et exposés via le tunnel Cloudflare. | |
| 17 | + | |
| 18 | +## Commandes | |
| 19 | + | |
| 20 | +```bash | |
| 21 | +# Développement local (port 3010) | |
| 22 | +npm run dev | |
| 23 | + | |
| 24 | +# Validation des fichiers Markdown et des liens internes | |
| 25 | +npm run check | |
| 26 | + | |
| 27 | +# Compilation statique (génère le dossier out/) | |
| 28 | +npm run build | |
| 29 | + | |
| 30 | +# Déploiement sur server-003 | |
| 31 | +npm run deploy | |
| 32 | +``` | |
Ajoutécontent/api/alertes.md+133−0
| @@ -0,0 +1,133 @@ | ||
| 1 | +--- | |
| 2 | +title: Alertes | |
| 3 | +description: Règles d'alerte (Discord, e-mail…) sur la consommation d'un service. | |
| 4 | +order: 10 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Consulter les règles d'alerte | |
| 8 | + | |
| 9 | +`GET /api/hosts/{id}/alerts` · permission `hosts:read` | |
| 10 | + | |
| 11 | +Liste les seuils configurés sur le CPU, la RAM, l'espace disque ou l'arrêt inopiné de l'instance. | |
| 12 | + | |
| 13 | +**Paramètres d'adresse** | |
| 14 | + | |
| 15 | +| Nom | Type | Requis | Description | | |
| 16 | +| --- | --- | --- | --- | | |
| 17 | +| `id` | string | oui | Identifiant du service | | |
| 18 | + | |
| 19 | +```bash | |
| 20 | +curl -X GET "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/alerts" \ | |
| 21 | + -H "Authorization: Bearer dim_live_…" | |
| 22 | +``` | |
| 23 | + | |
| 24 | +Réponse : | |
| 25 | + | |
| 26 | +```json | |
| 27 | +{ | |
| 28 | + "success": true, | |
| 29 | + "rules": [ | |
| 30 | + { | |
| 31 | + "id": "cly_alert_001", | |
| 32 | + "metricType": "CPU", | |
| 33 | + "thresholdValue": 90.0, | |
| 34 | + "durationSec": 600, | |
| 35 | + "channelType": "DISCORD_WEBHOOK", | |
| 36 | + "channelTarget": "https://discord.com/api/webhooks/...", | |
| 37 | + "enabled": true | |
| 38 | + } | |
| 39 | + ] | |
| 40 | +} | |
| 41 | +``` | |
| 42 | + | |
| 43 | +## Créer une règle d'alerte | |
| 44 | + | |
| 45 | +`POST /api/hosts/{id}/alerts` · permission `hosts:write` | |
| 46 | + | |
| 47 | +Configure une notification proactive envoyée sur Discord, par Email ou dans le tableau de bord en cas de dépassement critique de ressource. | |
| 48 | + | |
| 49 | +**Paramètres d'adresse** | |
| 50 | + | |
| 51 | +| Nom | Type | Requis | Description | | |
| 52 | +| --- | --- | --- | --- | | |
| 53 | +| `id` | string | oui | Identifiant du service | | |
| 54 | + | |
| 55 | +**Corps JSON** | |
| 56 | + | |
| 57 | +| Nom | Type | Requis | Description | | |
| 58 | +| --- | --- | --- | --- | | |
| 59 | +| `metricType` | string | oui | "CPU" \| "RAM" \| "DISK" \| "STATUS" | | |
| 60 | +| `thresholdValue` | number | oui | Pourcentage déclencheur (ex: 85.0) | | |
| 61 | +| `durationSec` | number | non | Durée continue avant alerte en secondes (défaut: 600) | | |
| 62 | +| `channelType` | string | oui | "DISCORD_WEBHOOK" \| "EMAIL" \| "IN_APP" | | |
| 63 | +| `channelTarget` | string | oui | URL du webhook Discord ou adresse email | | |
| 64 | + | |
| 65 | +```bash | |
| 66 | +curl -X POST "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/alerts" \ | |
| 67 | + -H "Authorization: Bearer dim_live_…" \ | |
| 68 | + -H "Content-Type: application/json" \ | |
| 69 | + -d '{ | |
| 70 | + "metricType": "CPU", | |
| 71 | + "thresholdValue": 90, | |
| 72 | + "durationSec": 600, | |
| 73 | + "channelType": "DISCORD_WEBHOOK", | |
| 74 | + "channelTarget": "https://discord.com/api/webhooks/123/abc" | |
| 75 | + }' | |
| 76 | +``` | |
| 77 | + | |
| 78 | +Réponse : | |
| 79 | + | |
| 80 | +```json | |
| 81 | +{ | |
| 82 | + "success": true, | |
| 83 | + "rule": { | |
| 84 | + "id": "cly_alert_002", | |
| 85 | + "metricType": "CPU", | |
| 86 | + "thresholdValue": 90, | |
| 87 | + "channelType": "DISCORD_WEBHOOK", | |
| 88 | + "enabled": true | |
| 89 | + } | |
| 90 | +} | |
| 91 | +``` | |
| 92 | + | |
| 93 | +## Modifier une règle d'alerte | |
| 94 | + | |
| 95 | +`PUT /api/hosts/{id}/alerts/{ruleId}` · permission `hosts:write` | |
| 96 | + | |
| 97 | +Seuls les champs envoyés changent : `metricType`, `thresholdValue`, `durationSec`, `cooldownMin` (délai en minutes entre deux alertes), `enabled`, `channelType`, `channelTarget`. | |
| 98 | + | |
| 99 | +```bash | |
| 100 | +curl -X PUT "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/alerts/RULE_ID" \ | |
| 101 | + -H "Authorization: Bearer dim_live_…" \ | |
| 102 | + -H "Content-Type: application/json" \ | |
| 103 | + -d '{"thresholdValue": 90, "enabled": true}' | |
| 104 | +``` | |
| 105 | + | |
| 106 | +## Supprimer une règle d'alerte | |
| 107 | + | |
| 108 | +`DELETE /api/hosts/{id}/alerts/{ruleId}` · permission `hosts:write` | |
| 109 | + | |
| 110 | +```bash | |
| 111 | +curl -X DELETE "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/alerts/RULE_ID" \ | |
| 112 | + -H "Authorization: Bearer dim_live_…" | |
| 113 | +``` | |
| 114 | + | |
| 115 | +## Tester un canal de notification | |
| 116 | + | |
| 117 | +`POST /api/hosts/{id}/alerts/test` · permission `hosts:write` | |
| 118 | + | |
| 119 | +Envoie un message de test, par exemple sur un webhook Discord, pour vérifier le canal avant de créer la règle. | |
| 120 | + | |
| 121 | +**Corps JSON** | |
| 122 | + | |
| 123 | +| Nom | Type | Requis | Description | | |
| 124 | +| --- | --- | --- | --- | | |
| 125 | +| `channelType` | string | oui | `"DISCORD_WEBHOOK"`, `"EMAIL"` ou `"IN_APP"` | | |
| 126 | +| `channelTarget` | string | oui | URL du webhook Discord (`https://discord.com/api/webhooks/…`) ou adresse e-mail | | |
| 127 | + | |
| 128 | +```bash | |
| 129 | +curl -X POST "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/alerts/test" \ | |
| 130 | + -H "Authorization: Bearer dim_live_…" \ | |
| 131 | + -H "Content-Type: application/json" \ | |
| 132 | + -d '{"channelType": "DISCORD_WEBHOOK", "channelTarget": "https://discord.com/api/webhooks/123/abc"}' | |
| 133 | +``` | |
Ajoutécontent/api/cli.md+76−0
| @@ -0,0 +1,76 @@ | ||
| 1 | +--- | |
| 2 | +title: CLI dimdams | |
| 3 | +description: Gérer vos services depuis un terminal, avec la même clé que l'API. | |
| 4 | +order: 1 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +La CLI `dimdams` est un seul fichier, sans dépendance. Elle utilise l'[API REST](/api/) et une [clé d'API](/api/#authentification). | |
| 8 | + | |
| 9 | +## Installation | |
| 10 | + | |
| 11 | +Il faut Node.js 18 ou plus récent. | |
| 12 | + | |
| 13 | +**Linux et macOS :** | |
| 14 | + | |
| 15 | +```bash | |
| 16 | +curl -fsSL {{MAIN_SITE_URL}}/cli/install.sh | sh | |
| 17 | +``` | |
| 18 | + | |
| 19 | +**Windows :** téléchargez [`dimdams.mjs`]({{MAIN_SITE_URL}}/cli/dimdams.mjs), puis lancez-le avec Node : | |
| 20 | + | |
| 21 | +```bash | |
| 22 | +node dimdams.mjs --help | |
| 23 | +``` | |
| 24 | + | |
| 25 | +## Se connecter | |
| 26 | + | |
| 27 | +```bash | |
| 28 | +dimdams login | |
| 29 | +``` | |
| 30 | + | |
| 31 | +La commande demande votre clé d'API et l'enregistre dans votre dossier de configuration (`~/.config/dimdams/config.json`, ou `%APPDATA%\dimdams\config.json` sous Windows). `dimdams whoami` affiche le compte et les permissions de la clé, `dimdams logout` la supprime. | |
| 32 | + | |
| 33 | +Dans une CI, passez plutôt la clé par une variable d'environnement, depuis les secrets du pipeline : | |
| 34 | + | |
| 35 | +```bash | |
| 36 | +DIMDAMS_API_KEY="$DIMDAMS_API_KEY" dimdams restart mon-site | |
| 37 | +``` | |
| 38 | + | |
| 39 | +## Commandes | |
| 40 | + | |
| 41 | +Un `<service>` se désigne par son identifiant, son nom ou son domaine. | |
| 42 | + | |
| 43 | +| Commande | Ce qu'elle fait | Permission | | |
| 44 | +| --- | --- | --- | | |
| 45 | +| `dimdams services` | Liste vos services | `hosts:read` | | |
| 46 | +| `dimdams status <service>` | Détails d'un service | `hosts:read` | | |
| 47 | +| `dimdams start <service>` | Démarre un service | `hosts:power` | | |
| 48 | +| `dimdams stop <service>` | Arrête un service | `hosts:power` | | |
| 49 | +| `dimdams restart <service>` | Redémarre un service | `hosts:power` | | |
| 50 | +| `dimdams logs <service>` | Affiche les journaux (`-n 200` pour le nombre de lignes, `--type docker\|access\|error`, `-f` pour suivre) | `hosts:read` | | |
| 51 | +| `dimdams deploy <service>` | Lance un déploiement Git | `hosts:write` | | |
| 52 | +| `dimdams backups <service>` | Liste les sauvegardes | `backups:read` | | |
| 53 | +| `dimdams backup <service>` | Crée une sauvegarde | `backups:write` | | |
| 54 | + | |
| 55 | +Exemples : | |
| 56 | + | |
| 57 | +```bash | |
| 58 | +dimdams services | |
| 59 | +dimdams restart mon-site | |
| 60 | +dimdams logs mon-bot -f | |
| 61 | +dimdams backup mon-site | |
| 62 | +``` | |
| 63 | + | |
| 64 | +## Options | |
| 65 | + | |
| 66 | +| Option | Effet | | |
| 67 | +| --- | --- | | |
| 68 | +| `--json` | Sortie JSON brute, pour vos scripts | | |
| 69 | +| `-h`, `--help` | Aide | | |
| 70 | +| `-v`, `--version` | Version | | |
| 71 | + | |
| 72 | +| Variable d'environnement | Effet | | |
| 73 | +| --- | --- | | |
| 74 | +| `DIMDAMS_API_KEY` | Clé d'API, prioritaire sur la clé enregistrée | | |
| 75 | +| `DIMDAMS_API_URL` | Adresse du panel (`{{MAIN_SITE_URL}}` par défaut) | | |
| 76 | +| `NO_COLOR` | Désactive les couleurs | | |
Ajoutécontent/api/deploiement-git.md+83−0
| @@ -0,0 +1,83 @@ | ||
| 1 | +--- | |
| 2 | +title: Déploiement Git | |
| 3 | +description: Relier un service à un dépôt Git pour le mettre à jour à chaque git push. | |
| 4 | +order: 5 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Récupérer la configuration Git & Webhook | |
| 8 | + | |
| 9 | +`GET /api/hosts/{id}/git` · permission `hosts:read` | |
| 10 | + | |
| 11 | +Renvoie l'URL du dépôt surveillé, la branche, le secret HMAC de signature et l'URL de Webhook à configurer sur GitHub/GitLab. | |
| 12 | + | |
| 13 | +**Paramètres d'adresse** | |
| 14 | + | |
| 15 | +| Nom | Type | Requis | Description | | |
| 16 | +| --- | --- | --- | --- | | |
| 17 | +| `id` | string | oui | Identifiant du service | | |
| 18 | + | |
| 19 | +```bash | |
| 20 | +curl -X GET "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/git" \ | |
| 21 | + -H "Authorization: Bearer dim_live_…" | |
| 22 | +``` | |
| 23 | + | |
| 24 | +Réponse : | |
| 25 | + | |
| 26 | +```json | |
| 27 | +{ | |
| 28 | + "success": true, | |
| 29 | + "gitDeployment": { | |
| 30 | + "id": "cly_git_001", | |
| 31 | + "repoUrl": "https://github.com/mon-orga/mon-api.git", | |
| 32 | + "branch": "main", | |
| 33 | + "autoDeploy": true, | |
| 34 | + "webhookSecret": "sec_8f9c1e4a...", | |
| 35 | + "webhookUrl": "{{MAIN_SITE_URL}}/api/webhooks/git/vps-1793740523964-d3bw0r", | |
| 36 | + "buildScript": "npm install && npm run build", | |
| 37 | + "deployStatus": "DEPLOYED", | |
| 38 | + "lastCommitHash": "8f1a9b2", | |
| 39 | + "lastCommitMsg": "feat: optimize database caching layer", | |
| 40 | + "lastDeployedAt": "2026-09-05T02:15:00.000Z" | |
| 41 | + } | |
| 42 | +} | |
| 43 | +``` | |
| 44 | + | |
| 45 | +## Déclencher un déploiement ou modifier le pipeline | |
| 46 | + | |
| 47 | +`POST /api/hosts/{id}/git` · permission `hosts:write` | |
| 48 | + | |
| 49 | +Permet de configurer le pipeline Git ou de forcer un redéploiement immédiat du dernier commit. | |
| 50 | + | |
| 51 | +**Paramètres d'adresse** | |
| 52 | + | |
| 53 | +| Nom | Type | Requis | Description | | |
| 54 | +| --- | --- | --- | --- | | |
| 55 | +| `id` | string | oui | Identifiant du service | | |
| 56 | + | |
| 57 | +**Corps JSON** | |
| 58 | + | |
| 59 | +| Nom | Type | Requis | Description | | |
| 60 | +| --- | --- | --- | --- | | |
| 61 | +| `action` | string | oui | "configure" (défaut), "trigger" (déploiement immédiat), ou "delete" | | |
| 62 | +| `repoUrl` | string | non | URL du dépôt (pour "configure") | | |
| 63 | +| `branch` | string | non | Branche (pour "configure") | | |
| 64 | + | |
| 65 | +```bash | |
| 66 | +curl -X POST "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/git" \ | |
| 67 | + -H "Authorization: Bearer dim_live_…" \ | |
| 68 | + -H "Content-Type: application/json" \ | |
| 69 | + -d '{"action": "trigger"}' | |
| 70 | +``` | |
| 71 | + | |
| 72 | +Réponse : | |
| 73 | + | |
| 74 | +```json | |
| 75 | +{ | |
| 76 | + "success": true, | |
| 77 | + "gitDeployment": { | |
| 78 | + "deployStatus": "DEPLOYED", | |
| 79 | + "lastCommitHash": "8f1a9b2", | |
| 80 | + "lastDeployedAt": "2026-09-05T03:30:00.000Z" | |
| 81 | + } | |
| 82 | +} | |
| 83 | +``` | |
Ajoutécontent/api/index.md+75−0
| @@ -0,0 +1,75 @@ | ||
| 1 | +--- | |
| 2 | +title: API & CLI | |
| 3 | +description: Piloter vos services Dimdam's depuis vos scripts, vos pipelines CI/CD ou un terminal. | |
| 4 | +--- | |
| 5 | + | |
| 6 | +L'API REST de Dimdam's permet d'automatiser ce que vous faites dans le tableau de bord : lister vos services, les démarrer ou les arrêter, lire leurs journaux, gérer les sauvegardes, le pare-feu, les ports, les domaines et les alertes. La [CLI `dimdams`](/api/cli/) utilise la même API et la même clé. | |
| 7 | + | |
| 8 | +## URL de base | |
| 9 | + | |
| 10 | +Toutes les routes sont relatives à cette adresse et répondent en JSON : | |
| 11 | + | |
| 12 | +```text | |
| 13 | +{{MAIN_SITE_URL}}/api | |
| 14 | +``` | |
| 15 | + | |
| 16 | +## Authentification | |
| 17 | + | |
| 18 | +Créez une clé dans **Paramètres › Clés d'API** de votre espace client, puis envoyez-la dans l'en-tête `Authorization` de chaque requête. Une clé commence toujours par `dim_live_`. | |
| 19 | + | |
| 20 | +```bash | |
| 21 | +curl "{{MAIN_SITE_URL}}/api/hosts" \ | |
| 22 | + -H "Authorization: Bearer dim_live_…" | |
| 23 | +``` | |
| 24 | + | |
| 25 | +> [!WARNING] | |
| 26 | +> La clé n'est affichée qu'une fois, à sa création. Gardez-la comme un mot de passe : dans un gestionnaire de secrets ou les secrets de votre CI, jamais dans le code d'un dépôt. Vous pouvez lui donner une date d'expiration, et la révoquer à tout moment depuis la même page. | |
| 27 | + | |
| 28 | +Pour vérifier une clé et voir ses permissions, appelez [`GET /api/user/me`](/api/services/#verifier-une-cle-api). | |
| 29 | + | |
| 30 | +## Permissions | |
| 31 | + | |
| 32 | +Chaque clé ne reçoit que les permissions dont elle a besoin. La permission demandée par chaque route est indiquée dans cette référence. | |
| 33 | + | |
| 34 | +| Permission | Ce qu'elle permet | | |
| 35 | +| --- | --- | | |
| 36 | +| `hosts:read` | Lire vos services, leur état, leurs métriques, leurs journaux et leur configuration | | |
| 37 | +| `hosts:write` | Modifier la configuration : pare-feu, ports, domaines, alertes, déploiement Git, planification des sauvegardes | | |
| 38 | +| `hosts:power` | Démarrer, arrêter et redémarrer vos services | | |
| 39 | +| `backups:read` | Lister les sauvegardes | | |
| 40 | +| `backups:write` | Créer une sauvegarde | | |
| 41 | +| `user:read` | Lire le journal d'audit de votre compte | | |
| 42 | +| `*` | Toutes les permissions | | |
| 43 | + | |
| 44 | +Une clé agit avec les droits de votre compte : elle voit vos services et ceux qu'une équipe partage avec vous, avec les droits que l'équipe vous a donnés. | |
| 45 | + | |
| 46 | +## Limites de débit | |
| 47 | + | |
| 48 | +Jusqu'à **120 requêtes par minute et par clé**. Au-delà, l'API répond `429`, avec le délai à attendre dans le message d'erreur. | |
| 49 | + | |
| 50 | +## Erreurs | |
| 51 | + | |
| 52 | +En cas d'erreur, la réponse contient `success: false` et un message en français : | |
| 53 | + | |
| 54 | +```json | |
| 55 | +{ | |
| 56 | + "success": false, | |
| 57 | + "error": "Permissions insuffisantes (scope requis : hosts:power)" | |
| 58 | +} | |
| 59 | +``` | |
| 60 | + | |
| 61 | +| Code | Signification | | |
| 62 | +| --- | --- | | |
| 63 | +| `400` | Requête invalide : un paramètre manque ou n'a pas le bon format | | |
| 64 | +| `401` | Clé absente, invalide, expirée ou révoquée, ou permission manquante pour cette route | | |
| 65 | +| `403` | Votre compte n'a pas le droit de faire cette action sur ce service (service partagé en lecture seule, par exemple) | | |
| 66 | +| `404` | Service introuvable, ou vous n'y avez pas accès | | |
| 67 | +| `429` | Limite de débit atteinte | | |
| 68 | +| `500` | Erreur de notre côté : réessayez, puis contactez le support si elle continue | | |
| 69 | + | |
| 70 | +## Identifiants des services | |
| 71 | + | |
| 72 | +Chaque service a un identifiant unique, par exemple `vps-1793740523964-d3bw0r` ou `web-1792669078523-xhyuv9`. Il apparaît dans l'adresse de la page du service dans le tableau de bord, et dans le champ `id` de [`GET /api/hosts`](/api/services/#lister-les-services-actifs). | |
| 73 | + | |
| 74 | +> [!NOTE] | |
| 75 | +> Les exemples de réponse de cette référence montrent les champs principaux. Les réponses réelles peuvent en contenir d'autres : ignorez ceux que vous n'utilisez pas. | |
Ajoutécontent/api/journal-audit.md+45−0
| @@ -0,0 +1,45 @@ | ||
| 1 | +--- | |
| 2 | +title: Journal d'audit | |
| 3 | +description: Historique des actions faites sur votre compte et vos services. | |
| 4 | +order: 11 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Consulter le journal d'audit du compte | |
| 8 | + | |
| 9 | +`GET /api/user/audit-logs` · permission `user:read` | |
| 10 | + | |
| 11 | +Retourne les 150 dernières actions effectuées sur votre compte et vos services (adresse IP, agent, état avant et après). Réservé aux clés à accès complet. | |
| 12 | + | |
| 13 | +```bash | |
| 14 | +curl -X GET "{{MAIN_SITE_URL}}/api/user/audit-logs" \ | |
| 15 | + -H "Authorization: Bearer dim_live_…" | |
| 16 | +``` | |
| 17 | + | |
| 18 | +Réponse : | |
| 19 | + | |
| 20 | +```json | |
| 21 | +{ | |
| 22 | + "success": true, | |
| 23 | + "logs": [ | |
| 24 | + { | |
| 25 | + "id": "cly_audit_999", | |
| 26 | + "action": "FIREWALL_RULE_CREATE", | |
| 27 | + "entityType": "SECURITY", | |
| 28 | + "ipAddress": "194.250.88.12", | |
| 29 | + "status": "SUCCESS", | |
| 30 | + "createdAt": "2026-09-05T03:15:00.000Z" | |
| 31 | + } | |
| 32 | + ] | |
| 33 | +} | |
| 34 | +``` | |
| 35 | + | |
| 36 | +## Journal d'un service | |
| 37 | + | |
| 38 | +`GET /api/hosts/{id}/audit-logs` · permission `hosts:read` | |
| 39 | + | |
| 40 | +Les 100 dernières actions faites sur un service (démarrage, sauvegarde, pare-feu, ports…), les plus récentes en premier, avec leur auteur. | |
| 41 | + | |
| 42 | +```bash | |
| 43 | +curl "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/audit-logs" \ | |
| 44 | + -H "Authorization: Bearer dim_live_…" | |
| 45 | +``` | |
Ajoutécontent/api/metriques.md+87−0
| @@ -0,0 +1,87 @@ | ||
| 1 | +--- | |
| 2 | +title: Métriques | |
| 3 | +description: Historique de consommation (CPU, mémoire, réseau) d'un service et de votre compte. | |
| 4 | +order: 3 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Historique des métriques (24h / 7j / 30j) | |
| 8 | + | |
| 9 | +`GET /api/hosts/{id}/stats/history` · permission `hosts:read` | |
| 10 | + | |
| 11 | +Récupère la série temporelle haute résolution des ressources consommées : pourcentage CPU, mémoire RAM (Mo), I/O disque (Go) et débit réseau entrant/sortant (Mo/s). | |
| 12 | + | |
| 13 | +**Paramètres d'adresse** | |
| 14 | + | |
| 15 | +| Nom | Type | Requis | Description | | |
| 16 | +| --- | --- | --- | --- | | |
| 17 | +| `id` | string | oui | Identifiant unique du service | | |
| 18 | + | |
| 19 | +**Paramètres de requête** | |
| 20 | + | |
| 21 | +| Nom | Type | Requis | Description | | |
| 22 | +| --- | --- | --- | --- | | |
| 23 | +| `range` | string | non | Période d'analyse : "24h" (défaut), "7d" ou "30d" | | |
| 24 | + | |
| 25 | +```bash | |
| 26 | +curl -X GET "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/stats/history?range=7d" \ | |
| 27 | + -H "Authorization: Bearer dim_live_…" | |
| 28 | +``` | |
| 29 | + | |
| 30 | +Réponse : | |
| 31 | + | |
| 32 | +```json | |
| 33 | +{ | |
| 34 | + "success": true, | |
| 35 | + "timeframe": "7d", | |
| 36 | + "dataPointsCount": 28, | |
| 37 | + "metrics": [ | |
| 38 | + { | |
| 39 | + "timestamp": "2026-09-01T12:00:00.000Z", | |
| 40 | + "cpuPercent": 18.4, | |
| 41 | + "ramMb": 1280.0, | |
| 42 | + "ramLimitMb": 4096.0, | |
| 43 | + "diskGb": 14.8, | |
| 44 | + "diskLimitGb": 80.0, | |
| 45 | + "networkRxMbps": 5.2, | |
| 46 | + "networkTxMbps": 3.8 | |
| 47 | + } | |
| 48 | + ] | |
| 49 | +} | |
| 50 | +``` | |
| 51 | + | |
| 52 | +## Agrégation globale du parc d'instances | |
| 53 | + | |
| 54 | +`GET /api/dashboard/stats/history` · permission `hosts:read` | |
| 55 | + | |
| 56 | +Fournit la vue consolidée de la consommation de ressources sur l'ensemble de vos services pour alimenter vos dashboards de monitoring d'entreprise. | |
| 57 | + | |
| 58 | +**Paramètres de requête** | |
| 59 | + | |
| 60 | +| Nom | Type | Requis | Description | | |
| 61 | +| --- | --- | --- | --- | | |
| 62 | +| `range` | string | non | Période : "24h" \| "7d" \| "30d" | | |
| 63 | + | |
| 64 | +```bash | |
| 65 | +curl -X GET "{{MAIN_SITE_URL}}/api/dashboard/stats/history?range=24h" \ | |
| 66 | + -H "Authorization: Bearer dim_live_…" | |
| 67 | +``` | |
| 68 | + | |
| 69 | +Réponse : | |
| 70 | + | |
| 71 | +```json | |
| 72 | +{ | |
| 73 | + "success": true, | |
| 74 | + "timeframe": "24h", | |
| 75 | + "activeHostsCount": 5, | |
| 76 | + "metrics": [ | |
| 77 | + { | |
| 78 | + "timestamp": "2026-09-05T00:00:00.000Z", | |
| 79 | + "avgCpu": 22.1, | |
| 80 | + "totalRamMb": 6450.0, | |
| 81 | + "totalDiskGb": 48.5, | |
| 82 | + "totalNetworkRxMbps": 18.4, | |
| 83 | + "totalNetworkTxMbps": 12.6 | |
| 84 | + } | |
| 85 | + ] | |
| 86 | +} | |
| 87 | +``` | |
Ajoutécontent/api/pare-feu.md+122−0
| @@ -0,0 +1,122 @@ | ||
| 1 | +--- | |
| 2 | +title: Pare-feu | |
| 3 | +description: Règles de filtrage des connexions entrantes d'un service. | |
| 4 | +order: 4 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Lister les règles de sécurité | |
| 8 | + | |
| 9 | +`GET /api/hosts/{id}/firewall` · permission `hosts:read` | |
| 10 | + | |
| 11 | +Renvoie toutes les règles de filtrage réseau (ACLs) appliquées au niveau matériel et noyau (iptables/nftables) sur le serveur. | |
| 12 | + | |
| 13 | +**Paramètres d'adresse** | |
| 14 | + | |
| 15 | +| Nom | Type | Requis | Description | | |
| 16 | +| --- | --- | --- | --- | | |
| 17 | +| `id` | string | oui | Identifiant du service | | |
| 18 | + | |
| 19 | +```bash | |
| 20 | +curl -X GET "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/firewall" \ | |
| 21 | + -H "Authorization: Bearer dim_live_…" | |
| 22 | +``` | |
| 23 | + | |
| 24 | +Réponse : | |
| 25 | + | |
| 26 | +```json | |
| 27 | +{ | |
| 28 | + "success": true, | |
| 29 | + "rules": [ | |
| 30 | + { | |
| 31 | + "id": "cly123456789", | |
| 32 | + "direction": "INBOUND", | |
| 33 | + "action": "ACCEPT", | |
| 34 | + "protocol": "TCP", | |
| 35 | + "portRange": "22", | |
| 36 | + "sourceCidr": "194.250.0.0/16", | |
| 37 | + "description": "Accès SSH VPN Entreprise", | |
| 38 | + "priority": 10, | |
| 39 | + "enabled": true | |
| 40 | + } | |
| 41 | + ] | |
| 42 | +} | |
| 43 | +``` | |
| 44 | + | |
| 45 | +## Créer une règle de filtrage à chaud | |
| 46 | + | |
| 47 | +`POST /api/hosts/{id}/firewall` · permission `hosts:write` | |
| 48 | + | |
| 49 | +Injecte instantanément une règle de pare-feu appliquée en temps réel sur la pile réseau sans redémarrage de la machine. | |
| 50 | + | |
| 51 | +**Paramètres d'adresse** | |
| 52 | + | |
| 53 | +| Nom | Type | Requis | Description | | |
| 54 | +| --- | --- | --- | --- | | |
| 55 | +| `id` | string | oui | Identifiant du service | | |
| 56 | + | |
| 57 | +**Corps JSON** | |
| 58 | + | |
| 59 | +| Nom | Type | Requis | Description | | |
| 60 | +| --- | --- | --- | --- | | |
| 61 | +| `direction` | string | oui | "INBOUND" (entrant) ou "OUTBOUND" (sortant) | | |
| 62 | +| `action` | string | oui | "ACCEPT" (autoriser) ou "DROP" (rejeter) | | |
| 63 | +| `protocol` | string | oui | "TCP" \| "UDP" \| "ICMP" \| "ANY" | | |
| 64 | +| `portRange` | string | non | Port unique (ex: "8080") ou plage (ex: "3000-3050") | | |
| 65 | +| `sourceCidr` | string | non | Masque CIDR autorisé (défaut: "0.0.0.0/0") | | |
| 66 | +| `description` | string | non | Note explicative pour l'équipe | | |
| 67 | +| `priority` | number | non | Priorité d'évaluation (défaut: 100) | | |
| 68 | + | |
| 69 | +```bash | |
| 70 | +curl -X POST "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/firewall" \ | |
| 71 | + -H "Authorization: Bearer dim_live_…" \ | |
| 72 | + -H "Content-Type: application/json" \ | |
| 73 | + -d '{ | |
| 74 | + "direction": "INBOUND", | |
| 75 | + "action": "ACCEPT", | |
| 76 | + "protocol": "TCP", | |
| 77 | + "portRange": "443", | |
| 78 | + "sourceCidr": "0.0.0.0/0", | |
| 79 | + "description": "Port HTTPS Web" | |
| 80 | + }' | |
| 81 | +``` | |
| 82 | + | |
| 83 | +Réponse : | |
| 84 | + | |
| 85 | +```json | |
| 86 | +{ | |
| 87 | + "success": true, | |
| 88 | + "rule": { | |
| 89 | + "id": "cly987654321", | |
| 90 | + "direction": "INBOUND", | |
| 91 | + "action": "ACCEPT", | |
| 92 | + "protocol": "TCP", | |
| 93 | + "portRange": "443", | |
| 94 | + "sourceCidr": "0.0.0.0/0", | |
| 95 | + "description": "Port HTTPS Web", | |
| 96 | + "priority": 100, | |
| 97 | + "enabled": true | |
| 98 | + } | |
| 99 | +} | |
| 100 | +``` | |
| 101 | + | |
| 102 | +## Modifier une règle | |
| 103 | + | |
| 104 | +`PUT /api/hosts/{id}/firewall/{ruleId}` · permission `hosts:write` | |
| 105 | + | |
| 106 | +Mêmes champs que la création, tous facultatifs : seuls les champs envoyés changent. La règle est appliquée tout de suite. | |
| 107 | + | |
| 108 | +```bash | |
| 109 | +curl -X PUT "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/firewall/RULE_ID" \ | |
| 110 | + -H "Authorization: Bearer dim_live_…" \ | |
| 111 | + -H "Content-Type: application/json" \ | |
| 112 | + -d '{"sourceCidr": "203.0.113.0/24"}' | |
| 113 | +``` | |
| 114 | + | |
| 115 | +## Supprimer une règle | |
| 116 | + | |
| 117 | +`DELETE /api/hosts/{id}/firewall/{ruleId}` · permission `hosts:write` | |
| 118 | + | |
| 119 | +```bash | |
| 120 | +curl -X DELETE "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/firewall/RULE_ID" \ | |
| 121 | + -H "Authorization: Bearer dim_live_…" | |
| 122 | +``` | |
Ajoutécontent/api/reseau.md+143−0
| @@ -0,0 +1,143 @@ | ||
| 1 | +--- | |
| 2 | +title: Réseau et ports | |
| 3 | +description: Ports ouverts et adresses d'un VPS. | |
| 4 | +order: 6 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Lister les ports ouverts et sous-domaines | |
| 8 | + | |
| 9 | +`GET /api/hosts/{id}/ports` · permission `hosts:read` | |
| 10 | + | |
| 11 | +Retourne les ports TCP/UDP ouverts sur le VPS, les sous-domaines routés ainsi que les quotas gratuits et suppléments. | |
| 12 | + | |
| 13 | +**Paramètres d'adresse** | |
| 14 | + | |
| 15 | +| Nom | Type | Requis | Description | | |
| 16 | +| --- | --- | --- | --- | | |
| 17 | +| `id` | string | oui | Identifiant du service VPS | | |
| 18 | + | |
| 19 | +```bash | |
| 20 | +curl -X GET "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/ports" \ | |
| 21 | + -H "Authorization: Bearer dim_live_…" | |
| 22 | +``` | |
| 23 | + | |
| 24 | +Réponse : | |
| 25 | + | |
| 26 | +```json | |
| 27 | +{ | |
| 28 | + "success": true, | |
| 29 | + "ports": [ | |
| 30 | + { | |
| 31 | + "id": "port_172839485", | |
| 32 | + "containerPort": 4000, | |
| 33 | + "protocol": "tcp", | |
| 34 | + "hostPort": 32775, | |
| 35 | + "label": "LiteLLM API", | |
| 36 | + "isFree": true | |
| 37 | + } | |
| 38 | + ], | |
| 39 | + "subdomains": [ | |
| 40 | + { | |
| 41 | + "id": "sub_172839485", | |
| 42 | + "subdomain": "mon-ia", | |
| 43 | + "targetPort": 4000, | |
| 44 | + "ssl": true, | |
| 45 | + "isFree": true | |
| 46 | + } | |
| 47 | + ], | |
| 48 | + "summary": { | |
| 49 | + "totalPorts": 1, | |
| 50 | + "freePorts": 3, | |
| 51 | + "billablePorts": 0, | |
| 52 | + "totalSubdomains": 1, | |
| 53 | + "freeSubdomains": 1, | |
| 54 | + "billableSubdomains": 0, | |
| 55 | + "totalMonthlyCostEUR": 0 | |
| 56 | + } | |
| 57 | +} | |
| 58 | +``` | |
| 59 | + | |
| 60 | +## Ouvrir un port réseau (TCP / UDP) | |
| 61 | + | |
| 62 | +`POST /api/hosts/{id}/ports` · permission `hosts:write` | |
| 63 | + | |
| 64 | +Ouvre un port interne sur le VPS et lui attribue dynamiquement un port hôte public accessible depuis Internet. | |
| 65 | + | |
| 66 | +**Paramètres d'adresse** | |
| 67 | + | |
| 68 | +| Nom | Type | Requis | Description | | |
| 69 | +| --- | --- | --- | --- | | |
| 70 | +| `id` | string | oui | Identifiant du service VPS | | |
| 71 | + | |
| 72 | +**Corps JSON** | |
| 73 | + | |
| 74 | +| Nom | Type | Requis | Description | | |
| 75 | +| --- | --- | --- | --- | | |
| 76 | +| `containerPort` | number | oui | Port interne (1 - 65535) | | |
| 77 | +| `protocol` | string | non | 'tcp' ou 'udp' (défaut: 'tcp') | | |
| 78 | +| `label` | string | non | Nom ou description du service | | |
| 79 | + | |
| 80 | +```bash | |
| 81 | +curl -X POST "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/ports" \ | |
| 82 | + -H "Authorization: Bearer dim_live_…" \ | |
| 83 | + -H "Content-Type: application/json" \ | |
| 84 | + -d '{"containerPort": 4000, "protocol": "tcp", "label": "LiteLLM API"}' | |
| 85 | +``` | |
| 86 | + | |
| 87 | +Réponse : | |
| 88 | + | |
| 89 | +```json | |
| 90 | +{ | |
| 91 | + "success": true, | |
| 92 | + "port": { | |
| 93 | + "id": "port_172839485", | |
| 94 | + "containerPort": 4000, | |
| 95 | + "protocol": "tcp", | |
| 96 | + "hostPort": 32775, | |
| 97 | + "label": "LiteLLM API", | |
| 98 | + "isFree": true | |
| 99 | + }, | |
| 100 | + "message": "Port 4000/TCP ouvert avec succès" | |
| 101 | +} | |
| 102 | +``` | |
| 103 | + | |
| 104 | +## Lier un domaine personnalisé à un port | |
| 105 | + | |
| 106 | +`POST /api/hosts/{id}/custom-domains` · permission `hosts:write` | |
| 107 | + | |
| 108 | +Relie votre propre domaine à un port web (HTTP) ouvert du VPS, avec certificat SSL automatique. Le domaine doit d'abord pointer vers Dimdam's : un enregistrement CNAME vers `connect.dimdams.com`. Voir [Adresses et domaines](/hebergement/adresses-et-domaines/). | |
| 109 | + | |
| 110 | +**Corps JSON** | |
| 111 | + | |
| 112 | +| Nom | Type | Requis | Description | | |
| 113 | +| --- | --- | --- | --- | | |
| 114 | +| `domain` | string | oui | Le domaine, sans `https://` (ex. `app.exemple.fr`) | | |
| 115 | +| `targetPort` | number | oui | Port web ouvert du VPS vers lequel envoyer le trafic | | |
| 116 | + | |
| 117 | +```bash | |
| 118 | +curl -X POST "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/custom-domains" \ | |
| 119 | + -H "Authorization: Bearer dim_live_…" \ | |
| 120 | + -H "Content-Type: application/json" \ | |
| 121 | + -d '{"domain": "app.exemple.fr", "targetPort": 4000}' | |
| 122 | +``` | |
| 123 | + | |
| 124 | +Réponse : | |
| 125 | + | |
| 126 | +```json | |
| 127 | +{ | |
| 128 | + "success": true, | |
| 129 | + "customDomain": { "id": "cd_1793812345678_k2j9x", "domain": "app.exemple.fr", "targetPort": 4000, "status": "active", "ssl": true } | |
| 130 | +} | |
| 131 | +``` | |
| 132 | + | |
| 133 | +## Vérifier un domaine personnalisé | |
| 134 | + | |
| 135 | +`GET /api/hosts/{id}/custom-domains/{customDomainId}` · permission `hosts:read` | |
| 136 | + | |
| 137 | +Contrôle de nouveau le DNS et redemande le certificat si besoin (utile quand le statut est « en attente »). | |
| 138 | + | |
| 139 | +## Retirer un domaine personnalisé | |
| 140 | + | |
| 141 | +`DELETE /api/hosts/{id}/custom-domains/{customDomainId}` · permission `hosts:write` | |
| 142 | + | |
| 143 | +Supprime le routage et le certificat du domaine. | |
Ajoutécontent/api/sauvegardes.md+119−0
| @@ -0,0 +1,119 @@ | ||
| 1 | +--- | |
| 2 | +title: Sauvegardes | |
| 3 | +description: Lister, créer et planifier les sauvegardes d'un service. | |
| 4 | +order: 9 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Lister les sauvegardes | |
| 8 | + | |
| 9 | +`GET /api/hosts/{id}/backups` · permission `backups:read` | |
| 10 | + | |
| 11 | +Retourne les sauvegardes du service, manuelles et automatiques, de la plus récente à la plus ancienne. | |
| 12 | + | |
| 13 | +**Paramètres d'adresse** | |
| 14 | + | |
| 15 | +| Nom | Type | Requis | Description | | |
| 16 | +| --- | --- | --- | --- | | |
| 17 | +| `id` | string | oui | Identifiant du service | | |
| 18 | + | |
| 19 | +```bash | |
| 20 | +curl -X GET "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/backups" \ | |
| 21 | + -H "Authorization: Bearer dim_live_…" | |
| 22 | +``` | |
| 23 | + | |
| 24 | +Réponse : | |
| 25 | + | |
| 26 | +```json | |
| 27 | +{ | |
| 28 | + "success": true, | |
| 29 | + "backups": [ | |
| 30 | + { | |
| 31 | + "name": "auto_2026-09-04T03-00-00.tar.gz", | |
| 32 | + "size": 145892034, | |
| 33 | + "created": "2026-09-04T03:00:12.000Z", | |
| 34 | + "type": "auto" | |
| 35 | + } | |
| 36 | + ] | |
| 37 | +} | |
| 38 | +``` | |
| 39 | + | |
| 40 | +## Créer une sauvegarde manuelle | |
| 41 | + | |
| 42 | +`POST /api/hosts/{id}/backups` · permission `backups:write` | |
| 43 | + | |
| 44 | +Lance une sauvegarde immédiate des données du service. Au plus 10 sauvegardes manuelles, et une toutes les 10 minutes. | |
| 45 | + | |
| 46 | +**Paramètres d'adresse** | |
| 47 | + | |
| 48 | +| Nom | Type | Requis | Description | | |
| 49 | +| --- | --- | --- | --- | | |
| 50 | +| `id` | string | oui | Identifiant du service | | |
| 51 | + | |
| 52 | +**Corps JSON** | |
| 53 | + | |
| 54 | +| Nom | Type | Requis | Description | | |
| 55 | +| --- | --- | --- | --- | | |
| 56 | +| `action` | string | oui | "create" | | |
| 57 | + | |
| 58 | +```bash | |
| 59 | +curl -X POST "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/backups" \ | |
| 60 | + -H "Authorization: Bearer dim_live_…" \ | |
| 61 | + -H "Content-Type: application/json" \ | |
| 62 | + -d '{"action": "create"}' | |
| 63 | +``` | |
| 64 | + | |
| 65 | +Réponse : | |
| 66 | + | |
| 67 | +```json | |
| 68 | +{ | |
| 69 | + "success": true, | |
| 70 | + "backup": { | |
| 71 | + "name": "manual_2026-09-05T14-21-07.tar.gz", | |
| 72 | + "size": 145903311, | |
| 73 | + "type": "manual" | |
| 74 | + } | |
| 75 | +} | |
| 76 | +``` | |
| 77 | + | |
| 78 | +## Consulter la planification | |
| 79 | + | |
| 80 | +`GET /api/hosts/{id}/backups/schedule` · permission `hosts:read` | |
| 81 | + | |
| 82 | +Retourne la planification des sauvegardes automatiques du service (`schedule` vaut `null` si aucune n'est enregistrée). | |
| 83 | + | |
| 84 | +```bash | |
| 85 | +curl "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/backups/schedule" \ | |
| 86 | + -H "Authorization: Bearer dim_live_…" | |
| 87 | +``` | |
| 88 | + | |
| 89 | +## Programmer les sauvegardes automatiques | |
| 90 | + | |
| 91 | +`PUT /api/hosts/{id}/backups/schedule` · permission `hosts:write` | |
| 92 | + | |
| 93 | +Crée ou remplace la planification. Il faut aussi avoir le droit de gérer les sauvegardes du service (propriétaire, ou membre d'équipe avec ce droit). | |
| 94 | + | |
| 95 | +**Corps JSON** | |
| 96 | + | |
| 97 | +| Nom | Type | Requis | Description | | |
| 98 | +| --- | --- | --- | --- | | |
| 99 | +| `enabled` | boolean | non | Active ou suspend la planification (défaut : `true`) | | |
| 100 | +| `frequency` | string | non | `"DAILY"` (chaque jour, défaut) ou `"WEEKLY"` | | |
| 101 | +| `executionHour` | number | non | Heure de lancement, de 0 à 23, en UTC (défaut : 3) | | |
| 102 | +| `retentionDaily` | number | non | Sauvegardes quotidiennes gardées, de 1 à 30 (défaut : 7) | | |
| 103 | +| `retentionWeekly` | number | non | Sauvegardes hebdomadaires gardées, de 1 à 12 (défaut : 4) | | |
| 104 | + | |
| 105 | +```bash | |
| 106 | +curl -X PUT "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/backups/schedule" \ | |
| 107 | + -H "Authorization: Bearer dim_live_…" \ | |
| 108 | + -H "Content-Type: application/json" \ | |
| 109 | + -d '{"frequency": "DAILY", "executionHour": 3, "retentionDaily": 7}' | |
| 110 | +``` | |
| 111 | + | |
| 112 | +Réponse : | |
| 113 | + | |
| 114 | +```json | |
| 115 | +{ | |
| 116 | + "success": true, | |
| 117 | + "schedule": { "enabled": true, "frequency": "DAILY", "executionHour": 3, "retentionDaily": 7, "retentionWeekly": 4, "nextRunAt": "2026-11-05T03:00:00.000Z" } | |
| 118 | +} | |
| 119 | +``` | |
Ajoutécontent/api/services.md+173−0
| @@ -0,0 +1,173 @@ | ||
| 1 | +--- | |
| 2 | +title: Services | |
| 3 | +description: Lister vos services, les consulter, les démarrer ou les arrêter, lire leurs journaux. | |
| 4 | +order: 2 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Lister les services actifs | |
| 8 | + | |
| 9 | +`GET /api/hosts` · permission `hosts:read` | |
| 10 | + | |
| 11 | +Retourne vos services et ceux qu'une équipe partage avec vous, avec leur état et leur consommation du moment. Avec une clé d'API, la configuration est renvoyée sans aucun secret (mots de passe, jetons). | |
| 12 | + | |
| 13 | +```bash | |
| 14 | +curl "{{MAIN_SITE_URL}}/api/hosts" \ | |
| 15 | + -H "Authorization: Bearer dim_live_…" | |
| 16 | +``` | |
| 17 | + | |
| 18 | +Réponse (champs principaux) : | |
| 19 | + | |
| 20 | +```json | |
| 21 | +{ | |
| 22 | + "success": true, | |
| 23 | + "hosts": [ | |
| 24 | + { | |
| 25 | + "id": "vps-1793740523964-d3bw0r", | |
| 26 | + "type": "vps", | |
| 27 | + "subtype": "ubuntu-24.04", | |
| 28 | + "name": "Xenon", | |
| 29 | + "status": "running", | |
| 30 | + "initialized": true, | |
| 31 | + "createdAt": "2026-11-03T18:02:11.000Z", | |
| 32 | + "isShared": false, | |
| 33 | + "cpu_usage": 3.4, | |
| 34 | + "ram_usage": 21.7, | |
| 35 | + "memory_usage": 1863270400, | |
| 36 | + "memory_limit": 8589934592, | |
| 37 | + "domain": null | |
| 38 | + } | |
| 39 | + ], | |
| 40 | + "stats": { "totalHosts": 4, "runningHosts": 3, "configuredHosts": 4, "unconfiguredHosts": 0 } | |
| 41 | +} | |
| 42 | +``` | |
| 43 | + | |
| 44 | +## Détails d'un serveur | |
| 45 | + | |
| 46 | +`GET /api/hosts/{id}` · permission `hosts:read` | |
| 47 | + | |
| 48 | +Récupère les métadonnées détaillées, l'état de santé temps réel et la configuration d'un service spécifique. | |
| 49 | + | |
| 50 | +**Paramètres d'adresse** | |
| 51 | + | |
| 52 | +| Nom | Type | Requis | Description | | |
| 53 | +| --- | --- | --- | --- | | |
| 54 | +| `id` | string | oui | Identifiant unique du service (ex: vps-1793740523964-d3bw0r) | | |
| 55 | + | |
| 56 | +```bash | |
| 57 | +curl -X GET "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r" \ | |
| 58 | + -H "Authorization: Bearer dim_live_…" | |
| 59 | +``` | |
| 60 | + | |
| 61 | +Réponse : | |
| 62 | + | |
| 63 | +```json | |
| 64 | +{ | |
| 65 | + "success": true, | |
| 66 | + "host": { | |
| 67 | + "id": "vps-1793740523964-d3bw0r", | |
| 68 | + "name": "Production-VPS-01", | |
| 69 | + "type": "vps", | |
| 70 | + "status": "active", | |
| 71 | + "port": 22, | |
| 72 | + "domain": "vps-01.dimdams.net", | |
| 73 | + "cpu_usage": 14.2, | |
| 74 | + "memory_usage": 1024, | |
| 75 | + "memory_limit": 4096, | |
| 76 | + "disk_usage": 12.5, | |
| 77 | + "disk_limit": 80.0 | |
| 78 | + } | |
| 79 | +} | |
| 80 | +``` | |
| 81 | + | |
| 82 | +## Contrôle de puissance (Start / Stop / Restart) | |
| 83 | + | |
| 84 | +`POST /api/hosts/{id}/actions` · permission `hosts:power` | |
| 85 | + | |
| 86 | +Démarre, arrête ou redémarre l'instance. Avec une clé API, seules ces trois actions sont permises : l'initialisation et la réinitialisation se font depuis le tableau de bord. | |
| 87 | + | |
| 88 | +**Paramètres d'adresse** | |
| 89 | + | |
| 90 | +| Nom | Type | Requis | Description | | |
| 91 | +| --- | --- | --- | --- | | |
| 92 | +| `id` | string | oui | Identifiant unique du service | | |
| 93 | + | |
| 94 | +**Corps JSON** | |
| 95 | + | |
| 96 | +| Nom | Type | Requis | Description | | |
| 97 | +| --- | --- | --- | --- | | |
| 98 | +| `action` | string | oui | Action à exécuter : "start" \| "stop" \| "restart" | | |
| 99 | + | |
| 100 | +```bash | |
| 101 | +curl -X POST "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/actions" \ | |
| 102 | + -H "Authorization: Bearer dim_live_…" \ | |
| 103 | + -H "Content-Type: application/json" \ | |
| 104 | + -d '{"action": "restart"}' | |
| 105 | +``` | |
| 106 | + | |
| 107 | +Réponse : | |
| 108 | + | |
| 109 | +```json | |
| 110 | +{ | |
| 111 | + "success": true | |
| 112 | +} | |
| 113 | +``` | |
| 114 | + | |
| 115 | +## Lire les logs | |
| 116 | + | |
| 117 | +`GET /api/hosts/{id}/logs` · permission `hosts:read` | |
| 118 | + | |
| 119 | +Retourne les dernières lignes des logs du service : sortie du conteneur (docker) ou, pour les serveurs web, journaux d'accès et d'erreurs. | |
| 120 | + | |
| 121 | +**Paramètres d'adresse** | |
| 122 | + | |
| 123 | +| Nom | Type | Requis | Description | | |
| 124 | +| --- | --- | --- | --- | | |
| 125 | +| `id` | string | oui | Identifiant unique du service | | |
| 126 | + | |
| 127 | +**Paramètres de requête** | |
| 128 | + | |
| 129 | +| Nom | Type | Requis | Description | | |
| 130 | +| --- | --- | --- | --- | | |
| 131 | +| `type` | string | non | "docker" (défaut) \| "access" \| "error" | | |
| 132 | +| `lines` | number | non | Nombre de lignes, de 1 à 1000 (défaut : 100) | | |
| 133 | + | |
| 134 | +```bash | |
| 135 | +curl -X GET "{{MAIN_SITE_URL}}/api/hosts/vps-1793740523964-d3bw0r/logs?lines=200" \ | |
| 136 | + -H "Authorization: Bearer dim_live_…" | |
| 137 | +``` | |
| 138 | + | |
| 139 | +Réponse : | |
| 140 | + | |
| 141 | +```json | |
| 142 | +{ | |
| 143 | + "success": true, | |
| 144 | + "type": "docker", | |
| 145 | + "logs": "Server listening on port 3000\nGET / 200 4ms" | |
| 146 | +} | |
| 147 | +``` | |
| 148 | + | |
| 149 | +## Vérifier une clé API | |
| 150 | + | |
| 151 | +`GET /api/user/me` · permission `aucun` | |
| 152 | + | |
| 153 | +Retourne le compte associé à la clé, ses permissions et sa date d'expiration. Aucune permission particulière n'est requise. | |
| 154 | + | |
| 155 | +```bash | |
| 156 | +curl -X GET "{{MAIN_SITE_URL}}/api/user/me" \ | |
| 157 | + -H "Authorization: Bearer dim_live_…" | |
| 158 | +``` | |
| 159 | + | |
| 160 | +Réponse : | |
| 161 | + | |
| 162 | +```json | |
| 163 | +{ | |
| 164 | + "success": true, | |
| 165 | + "user": { "id": 42, "username": "alice", "email": "alice@example.com" }, | |
| 166 | + "apiKey": { | |
| 167 | + "name": "CI", | |
| 168 | + "prefix": "dim_live_9f8e7d6c...", | |
| 169 | + "scopes": ["hosts:read", "hosts:power"], | |
| 170 | + "expiresAt": null | |
| 171 | + } | |
| 172 | +} | |
| 173 | +``` | |
Ajoutécontent/git/api.md+53−0
| @@ -0,0 +1,53 @@ | ||
| 1 | +--- | |
| 2 | +title: API | |
| 3 | +description: Automatiser Dimdam's Git avec son API REST. | |
| 4 | +order: 6 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Dimdam's Git a sa propre API REST, différente de l'[API du panel](/api/). Elle permet de gérer les dépôts, les tickets, les demandes de fusion, les versions, les organisations, les webhooks… | |
| 8 | + | |
| 9 | +## URL de base | |
| 10 | + | |
| 11 | +```text | |
| 12 | +{{GIT_URL}}/api/v1 | |
| 13 | +``` | |
| 14 | + | |
| 15 | +La liste complète des routes, avec un formulaire pour les essayer, est sur [{{GIT_URL}}/api/swagger]({{GIT_URL}}/api/swagger). | |
| 16 | + | |
| 17 | +## Authentification | |
| 18 | + | |
| 19 | +Créez un [jeton d'accès](/git/premiers-pas/#jetons-dacces) avec les droits nécessaires, puis envoyez-le dans l'en-tête `Authorization` : | |
| 20 | + | |
| 21 | +```bash | |
| 22 | +curl "{{GIT_URL}}/api/v1/user" \ | |
| 23 | + -H "Authorization: token VOTRE_JETON" | |
| 24 | +``` | |
| 25 | + | |
| 26 | +## Exemples | |
| 27 | + | |
| 28 | +Lister vos dépôts : | |
| 29 | + | |
| 30 | +```bash | |
| 31 | +curl "{{GIT_URL}}/api/v1/user/repos" \ | |
| 32 | + -H "Authorization: token VOTRE_JETON" | |
| 33 | +``` | |
| 34 | + | |
| 35 | +Créer un dépôt privé : | |
| 36 | + | |
| 37 | +```bash | |
| 38 | +curl -X POST "{{GIT_URL}}/api/v1/user/repos" \ | |
| 39 | + -H "Authorization: token VOTRE_JETON" \ | |
| 40 | + -H "Content-Type: application/json" \ | |
| 41 | + -d '{"name": "mon-projet", "private": true}' | |
| 42 | +``` | |
| 43 | + | |
| 44 | +Ouvrir un ticket : | |
| 45 | + | |
| 46 | +```bash | |
| 47 | +curl -X POST "{{GIT_URL}}/api/v1/repos/votre-compte/mon-projet/issues" \ | |
| 48 | + -H "Authorization: token VOTRE_JETON" \ | |
| 49 | + -H "Content-Type: application/json" \ | |
| 50 | + -d '{"title": "La page d’accueil ne charge pas", "body": "Étapes pour reproduire…"}' | |
| 51 | +``` | |
| 52 | + | |
| 53 | +Les listes sont paginées avec `?page=` et `?limit=` (50 éléments au plus par page). | |
Ajoutécontent/git/automatisation.md+58−0
| @@ -0,0 +1,58 @@ | ||
| 1 | +--- | |
| 2 | +title: CI et webhooks | |
| 3 | +description: Lancer des tâches à chaque push, et prévenir vos outils des événements d'un dépôt. | |
| 4 | +order: 3 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## CI | |
| 8 | + | |
| 9 | +La CI lance des tâches à chaque `push`, à chaque demande de fusion ou à la main : tests, construction, publication. Les workflows s'écrivent en YAML dans le dossier `.github/workflows/` du dépôt, avec la même syntaxe que GitHub Actions. | |
| 10 | + | |
| 11 | +```yaml | |
| 12 | +# .github/workflows/tests.yml | |
| 13 | +name: Tests | |
| 14 | +on: | |
| 15 | + push: | |
| 16 | + pull_request: | |
| 17 | +jobs: | |
| 18 | + tests: | |
| 19 | + runs-on: ubuntu-latest | |
| 20 | + steps: | |
| 21 | + - uses: actions/checkout@v4 | |
| 22 | + - uses: actions/setup-node@v4 | |
| 23 | + with: | |
| 24 | + node-version: 22 | |
| 25 | + - run: npm ci | |
| 26 | + - run: npm test | |
| 27 | +``` | |
| 28 | + | |
| 29 | +Suivez les exécutions dans l'onglet **Actions** du dépôt : journaux en direct, annulation, relance, lancement manuel avec paramètres. | |
| 30 | + | |
| 31 | +### Secrets et variables | |
| 32 | + | |
| 33 | +Ajoutez vos secrets (jetons, clés d'API) dans **Paramètres › CI** du dépôt ou de l'organisation, et utilisez-les avec `${{ secrets.MON_SECRET }}`. Ils n'apparaissent jamais en clair dans les journaux. | |
| 34 | + | |
| 35 | +### Cache | |
| 36 | + | |
| 37 | +`actions/cache` garde vos dépendances d'une exécution à l'autre, pour aller plus vite. Le cache est propre à chaque dépôt. | |
| 38 | + | |
| 39 | +### Limites | |
| 40 | + | |
| 41 | +| | | | |
| 42 | +| --- | --- | | |
| 43 | +| Ressources d'une tâche | 2 processeurs, 4 Go de mémoire | | |
| 44 | +| Durée d'une tâche | 60 minutes au plus | | |
| 45 | +| Minutes par mois | 500 offertes, 2 000 avec Git Pro | | |
| 46 | +| Artefacts | Conservés 14 jours | | |
| 47 | + | |
| 48 | +Les tâches tournent dans des conteneurs jetables, isolés du réseau interne de Dimdam's. À la limite du mois, la CI des dépôts du compte s'arrête jusqu'au mois suivant (les tâches déjà lancées se terminent). | |
| 49 | + | |
| 50 | +## Webhooks | |
| 51 | + | |
| 52 | +Un webhook envoie une requête HTTP à votre outil à chaque événement d'un dépôt (push, ticket, demande de fusion, version publiée, exécution de CI…). Ajoutez-le dans **Paramètres › Webhooks** du dépôt ou de l'organisation. | |
| 53 | + | |
| 54 | +Chaque envoi est signé avec votre secret (HMAC) : vérifiez la signature avant de faire confiance au contenu. Les en-têtes sont compatibles avec ceux de GitHub, ce qui permet d'utiliser la plupart des outils existants. | |
| 55 | + | |
| 56 | +## Déployer sur un service Dimdam's | |
| 57 | + | |
| 58 | +Pour mettre à jour un site ou un bot Dimdam's à chaque `push`, voir [Déploiement Git](/hebergement/deploiement-git/). | |
Ajoutécontent/git/collaboration.md+33−0
| @@ -0,0 +1,33 @@ | ||
| 1 | +--- | |
| 2 | +title: Collaboration | |
| 3 | +description: Tickets, demandes de fusion, revues, organisations et équipes. | |
| 4 | +order: 2 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Organisations et équipes | |
| 8 | + | |
| 9 | +Une **organisation** regroupe les dépôts d'un projet ou d'une entreprise. Ses membres sont répartis en **équipes**, avec des droits sur les dépôts (lecture, écriture, administration). Vous pouvez aussi ajouter des **collaborateurs** directement à un dépôt. | |
| 10 | + | |
| 11 | +## Tickets | |
| 12 | + | |
| 13 | +Les tickets servent à suivre les bugs et les tâches, avec des étiquettes, des jalons et des personnes assignées. Une conversation peut être verrouillée. | |
| 14 | + | |
| 15 | +## Demandes de fusion | |
| 16 | + | |
| 17 | +Une demande de fusion propose de fusionner une branche dans une autre : | |
| 18 | + | |
| 19 | +- comparez les branches et ouvrez la demande depuis la page de comparaison ; | |
| 20 | +- relisez les différences, commentez une ligne ou un bloc de lignes (Maj + clic), approuvez ou demandez des modifications ; | |
| 21 | +- fusionnez par un commit de fusion, un « squash » ou un « rebase ». | |
| 22 | + | |
| 23 | +## Branches protégées | |
| 24 | + | |
| 25 | +Protégez une branche (par exemple `main`) dans **Paramètres › Branches** : interdiction de pousser directement, approbations obligatoires, vérifications de la CI avant la fusion. | |
| 26 | + | |
| 27 | +## Notifications | |
| 28 | + | |
| 29 | +Dimdam's Git vous prévient des mentions, des assignations et de l'activité des dépôts que vous suivez, dans l'application et par e-mail. Réglez ce que vous recevez dans **Paramètres › Notifications & e-mails**. | |
| 30 | + | |
| 31 | +## Wiki | |
| 32 | + | |
| 33 | +Chaque dépôt peut avoir un wiki en Markdown, avec des liens `[[Page]]` et son propre historique. Il se clone comme un dépôt : `mon-projet.wiki.git`. | |
Ajoutécontent/git/index.md+27−0
| @@ -0,0 +1,27 @@ | ||
| 1 | +--- | |
| 2 | +title: Dimdam's Git | |
| 3 | +description: Héberger vos dépôts Git en France : code, collaboration, CI, paquets et sites statiques. | |
| 4 | +--- | |
| 5 | + | |
| 6 | +[Dimdam's Git]({{GIT_URL}}) héberge vos dépôts sur les serveurs de Dimdam's, en France. On s'y connecte avec son compte Dimdam's, et on y trouve tout ce qu'il faut pour travailler à plusieurs : | |
| 7 | + | |
| 8 | +- **le code :** dépôts publics ou privés, branches, historique, recherche, éditeur en ligne, import depuis GitHub ou GitLab ; | |
| 9 | +- **la collaboration :** tickets, demandes de fusion avec revue ligne par ligne, branches protégées, organisations et équipes ; | |
| 10 | +- **l'automatisation :** CI à chaque `push`, webhooks, déploiement sur vos services Dimdam's ; | |
| 11 | +- **la publication :** versions publiées, registre de paquets et de conteneurs, sites statiques avec Pages. | |
| 12 | + | |
| 13 | +## Premiers pas | |
| 14 | + | |
| 15 | +1. Connectez-vous sur [{{GIT_URL}}]({{GIT_URL}}) avec votre compte Dimdam's : votre compte Git est créé à la première connexion. | |
| 16 | +2. Ajoutez votre clé SSH (voir [Premiers pas](/git/premiers-pas/#cles-ssh)). | |
| 17 | +3. Créez un dépôt, ou importez-en un depuis GitHub, GitLab ou un autre serveur Git. | |
| 18 | + | |
| 19 | +## Offre | |
| 20 | + | |
| 21 | +| | Gratuit | Dimdam's Git Pro | | |
| 22 | +| --- | --- | --- | | |
| 23 | +| Espace | 1 Go | 5 à 500 Go en plus | | |
| 24 | +| Dépôts | 25 | Sans limite | | |
| 25 | +| CI | 500 minutes par mois | 2 000 minutes par mois | | |
| 26 | + | |
| 27 | +Git Pro s'achète dans la [boutique]({{MAIN_SITE_URL}}/shop) et s'applique automatiquement. Un compte Git Pro peut faire profiter de son offre jusqu'à 3 de ses organisations. | |
Ajoutécontent/git/pages.md+58−0
| @@ -0,0 +1,58 @@ | ||
| 1 | +--- | |
| 2 | +title: Pages | |
| 3 | +description: Publier un site statique depuis un dépôt, avec HTTPS et votre propre domaine. | |
| 4 | +order: 4 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Pages publie un site statique (HTML, CSS, JavaScript, images) depuis un de vos dépôts, gratuitement, avec un certificat HTTPS automatique. | |
| 8 | + | |
| 9 | +## Publier un site | |
| 10 | + | |
| 11 | +Deux façons de faire : | |
| 12 | + | |
| 13 | +- **un dépôt nommé `pages` :** son contenu est publié à l'adresse `https://<compte>.pages.dimdams.net` ; | |
| 14 | +- **une branche `pages` dans n'importe quel dépôt :** son contenu est publié à l'adresse `https://<compte>.pages.dimdams.net/<dépôt>/`. | |
| 15 | + | |
| 16 | +Le fichier `index.html` à la racine sert de page d'accueil, et un `404.html` remplace la page d'erreur par défaut. | |
| 17 | + | |
| 18 | +## Construire le site avec la CI | |
| 19 | + | |
| 20 | +Si votre site se construit (Vite, Astro, Hugo…), laissez la [CI](/git/automatisation/) pousser le résultat dans la branche `pages` à chaque `push` sur `main` : | |
| 21 | + | |
| 22 | +```yaml | |
| 23 | +# .github/workflows/pages.yml | |
| 24 | +name: Pages | |
| 25 | +on: | |
| 26 | + push: | |
| 27 | + branches: [main] | |
| 28 | +jobs: | |
| 29 | + publier: | |
| 30 | + runs-on: ubuntu-latest | |
| 31 | + steps: | |
| 32 | + - uses: actions/checkout@v4 | |
| 33 | + - uses: actions/setup-node@v4 | |
| 34 | + with: | |
| 35 | + node-version: 22 | |
| 36 | + - run: npm ci && npm run build | |
| 37 | + - name: Pousser dans la branche pages | |
| 38 | + run: | | |
| 39 | + cd dist | |
| 40 | + git init -b pages | |
| 41 | + git add -A | |
| 42 | + git -c user.name=ci -c user.email=ci@localhost commit -m "Publication" | |
| 43 | + git push -f "https://ci:${{ secrets.PAGES_TOKEN }}@git.dimdams.com/${{ github.repository }}.git" pages | |
| 44 | +``` | |
| 45 | + | |
| 46 | +`PAGES_TOKEN` est un [jeton d'accès](/git/premiers-pas/#jetons-dacces) avec le droit d'écriture sur le dépôt, ajouté dans les secrets du dépôt. | |
| 47 | + | |
| 48 | +## Domaine personnalisé | |
| 49 | + | |
| 50 | +1. Ajoutez votre domaine dans **Paramètres › Pages** du dépôt. | |
| 51 | +2. Chez votre registraire, faites-le pointer vers Pages : | |
| 52 | + - pour un sous-domaine (`www.exemple.fr`), un enregistrement **CNAME** vers `<compte>.pages.dimdams.net` ; | |
| 53 | + - pour un domaine racine (`exemple.fr`), l'enregistrement **A** indiqué dans les paramètres ; | |
| 54 | + - et dans les deux cas, l'enregistrement **TXT** `_dimdams-pages` indiqué, qui prouve que le domaine est à vous. | |
| 55 | +3. Une fois le DNS propagé, le certificat HTTPS est créé automatiquement. | |
| 56 | + | |
| 57 | +> [!NOTE] | |
| 58 | +> Les noms en `dimdams.com` sont réservés à Dimdam's et ne peuvent pas servir de domaine personnalisé. | |
Ajoutécontent/git/paquets.md+80−0
| @@ -0,0 +1,80 @@ | ||
| 1 | +--- | |
| 2 | +title: Paquets et conteneurs | |
| 3 | +description: Publier des images de conteneur et des paquets npm, PyPI ou Maven. | |
| 4 | +order: 5 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Chaque compte et chaque organisation a son registre de paquets. Les paquets apparaissent dans l'onglet **Paquets** du profil, et peuvent être liés à un dépôt. Pour vous authentifier, utilisez votre nom d'utilisateur et un [jeton d'accès](/git/premiers-pas/#jetons-dacces) avec les droits sur les paquets. | |
| 8 | + | |
| 9 | +## Images de conteneur | |
| 10 | + | |
| 11 | +```bash | |
| 12 | +docker login git.dimdams.com | |
| 13 | +docker build -t git.dimdams.com/votre-compte/mon-app:1.0 . | |
| 14 | +docker push git.dimdams.com/votre-compte/mon-app:1.0 | |
| 15 | +``` | |
| 16 | + | |
| 17 | +Et pour la récupérer, par exemple sur un VPS : | |
| 18 | + | |
| 19 | +```bash | |
| 20 | +docker pull git.dimdams.com/votre-compte/mon-app:1.0 | |
| 21 | +``` | |
| 22 | + | |
| 23 | +## npm | |
| 24 | + | |
| 25 | +```bash | |
| 26 | +npm config set @votre-compte:registry {{GIT_URL}}/api/packages/votre-compte/npm/ | |
| 27 | +npm config set -- "//git.dimdams.com/api/packages/votre-compte/npm/:_authToken" "VOTRE_JETON" | |
| 28 | +npm publish | |
| 29 | +``` | |
| 30 | + | |
| 31 | +Le nom du paquet doit commencer par `@votre-compte/` dans `package.json`. | |
| 32 | + | |
| 33 | +## PyPI | |
| 34 | + | |
| 35 | +```bash | |
| 36 | +python -m twine upload \ | |
| 37 | + --repository-url {{GIT_URL}}/api/packages/votre-compte/pypi \ | |
| 38 | + -u votre-compte -p VOTRE_JETON dist/* | |
| 39 | +``` | |
| 40 | + | |
| 41 | +Pour l'installer : | |
| 42 | + | |
| 43 | +```bash | |
| 44 | +pip install --index-url {{GIT_URL}}/api/packages/votre-compte/pypi/simple/ mon-paquet | |
| 45 | +``` | |
| 46 | + | |
| 47 | +## Maven | |
| 48 | + | |
| 49 | +Dans `pom.xml` : | |
| 50 | + | |
| 51 | +```xml | |
| 52 | +<distributionManagement> | |
| 53 | + <repository> | |
| 54 | + <id>dimdams</id> | |
| 55 | + <url>{{GIT_URL}}/api/packages/votre-compte/maven</url> | |
| 56 | + </repository> | |
| 57 | +</distributionManagement> | |
| 58 | +``` | |
| 59 | + | |
| 60 | +Et dans `~/.m2/settings.xml`, le jeton associé à l'identifiant `dimdams` : | |
| 61 | + | |
| 62 | +```xml | |
| 63 | +<servers> | |
| 64 | + <server> | |
| 65 | + <id>dimdams</id> | |
| 66 | + <configuration> | |
| 67 | + <httpHeaders> | |
| 68 | + <property> | |
| 69 | + <name>Authorization</name> | |
| 70 | + <value>token VOTRE_JETON</value> | |
| 71 | + </property> | |
| 72 | + </httpHeaders> | |
| 73 | + </configuration> | |
| 74 | + </server> | |
| 75 | +</servers> | |
| 76 | +``` | |
| 77 | + | |
| 78 | +## Espace utilisé | |
| 79 | + | |
| 80 | +Les paquets comptent dans l'espace de votre compte (1 Go offert, plus avec Git Pro). Supprimez les anciennes versions depuis la page du paquet pour libérer de la place. | |
Ajoutécontent/git/premiers-pas.md+62−0
| @@ -0,0 +1,62 @@ | ||
| 1 | +--- | |
| 2 | +title: Premiers pas | |
| 3 | +description: Se connecter, ajouter une clé SSH, cloner et pousser. | |
| 4 | +order: 1 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Se connecter | |
| 8 | + | |
| 9 | +Dimdam's Git utilise votre compte Dimdam's : cliquez sur **Se connecter** sur [{{GIT_URL}}]({{GIT_URL}}). Votre compte Git est créé à la première connexion. | |
| 10 | + | |
| 11 | +## Clés SSH | |
| 12 | + | |
| 13 | +Pour cloner et pousser en SSH sans mot de passe, ajoutez votre clé publique dans **Paramètres › Clés SSH**. | |
| 14 | + | |
| 15 | +Vous n'avez pas encore de clé ? Créez-en une : | |
| 16 | + | |
| 17 | +```bash | |
| 18 | +ssh-keygen -t ed25519 -C "vous@exemple.fr" | |
| 19 | +cat ~/.ssh/id_ed25519.pub | |
| 20 | +``` | |
| 21 | + | |
| 22 | +Copiez la ligne affichée dans Dimdam's Git, puis testez : | |
| 23 | + | |
| 24 | +```bash | |
| 25 | +ssh -T git@git.dimdams.com | |
| 26 | +``` | |
| 27 | + | |
| 28 | +## Cloner et pousser | |
| 29 | + | |
| 30 | +En SSH (recommandé) : | |
| 31 | + | |
| 32 | +```bash | |
| 33 | +git clone git@git.dimdams.com:votre-compte/mon-projet.git | |
| 34 | +``` | |
| 35 | + | |
| 36 | +En HTTPS, avec un [jeton d'accès](#jetons-dacces) comme mot de passe : | |
| 37 | + | |
| 38 | +```bash | |
| 39 | +git clone {{GIT_URL}}/votre-compte/mon-projet.git | |
| 40 | +``` | |
| 41 | + | |
| 42 | +Pour envoyer un projet existant dans un nouveau dépôt : | |
| 43 | + | |
| 44 | +```bash | |
| 45 | +git remote add origin git@git.dimdams.com:votre-compte/mon-projet.git | |
| 46 | +git push -u origin main | |
| 47 | +``` | |
| 48 | + | |
| 49 | +## Jetons d'accès | |
| 50 | + | |
| 51 | +Les jetons d'accès servent à cloner et pousser en HTTPS, à appeler l'[API](/git/api/) et à utiliser le [registre de paquets](/git/paquets/). Créez-les dans **Paramètres › Jetons d'accès**, avec seulement les droits nécessaires. | |
| 52 | + | |
| 53 | +> [!WARNING] | |
| 54 | +> Un jeton n'est affiché qu'une fois. Traitez-le comme un mot de passe, et révoquez-le s'il a pu fuiter. | |
| 55 | + | |
| 56 | +## Clés GPG | |
| 57 | + | |
| 58 | +Ajoutez votre clé GPG dans **Paramètres › Clés GPG** : vos commits signés apparaîtront comme vérifiés. | |
| 59 | + | |
| 60 | +## Importer un dépôt | |
| 61 | + | |
| 62 | +**Nouveau dépôt › Importer** récupère un dépôt depuis GitHub, GitLab ou n'importe quelle adresse Git. Vous pouvez le garder synchronisé (miroir) toutes les heures, 8 heures ou 24 heures. | |
Ajoutécontent/hebergement/adresses-et-domaines.md+45−0
| @@ -0,0 +1,45 @@ | ||
| 1 | +--- | |
| 2 | +title: Adresses et domaines | |
| 3 | +description: L'adresse fournie avec chaque service, et comment utiliser votre propre nom de domaine. | |
| 4 | +order: 6 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## L'adresse de votre service | |
| 8 | + | |
| 9 | +Chaque service reçoit une adresse en `dimdams.net`, avec un certificat HTTPS automatique : | |
| 10 | + | |
| 11 | +| Service | Adresse | | |
| 12 | +| --- | --- | | |
| 13 | +| Site web | `votre-nom.dimdams.net` | | |
| 14 | +| Serveur de jeu | `votre-nom.play.dimdams.net`, avec le port du jeu | | |
| 15 | +| VPS | `votre-nom.vps.dimdams.net` (sous-domaine lié à un port) | | |
| 16 | + | |
| 17 | +> [!NOTE] | |
| 18 | +> Les adresses créées avant novembre 2026 en `dimdams.com` continuent de fonctionner. | |
| 19 | + | |
| 20 | +## Utiliser votre propre domaine | |
| 21 | + | |
| 22 | +Vous pouvez relier un domaine acheté chez n'importe quel registraire (OVH, Gandi, Cloudflare…) à votre service. | |
| 23 | + | |
| 24 | +### 1. Faire pointer le domaine vers Dimdam's | |
| 25 | + | |
| 26 | +Chez votre registraire, créez un enregistrement DNS : | |
| 27 | + | |
| 28 | +| Type | Nom | Cible | | |
| 29 | +| --- | --- | --- | | |
| 30 | +| `CNAME` | `app` (pour `app.exemple.fr`) | `connect.dimdams.com` | | |
| 31 | + | |
| 32 | +Pour un VPS, vous pouvez aussi ajouter un enregistrement `AAAA` vers son adresse IPv6 dédiée (onglet **Réseau**). | |
| 33 | + | |
| 34 | +> [!WARNING] | |
| 35 | +> Si votre DNS est chez Cloudflare, mettez l'enregistrement en « DNS uniquement » (nuage gris), sans proxy. La propagation peut prendre de quelques minutes à quelques heures. | |
| 36 | + | |
| 37 | +### 2. Lier le domaine dans le tableau de bord | |
| 38 | + | |
| 39 | +- **Site web :** dans la page du service, section domaine personnalisé. | |
| 40 | +- **VPS :** dans **Réseau**, bouton **Domaine Personnalisé**. Choisissez le port web de destination et entrez le domaine. Pas besoin de créer de sous-domaine. | |
| 41 | + | |
| 42 | +Dimdam's vérifie le DNS, puis obtient le certificat HTTPS (Let's Encrypt). Si le DNS n'est pas encore propagé, le domaine reste « en attente » : le bouton de vérification relance le contrôle. | |
| 43 | + | |
| 44 | +> [!NOTE] | |
| 45 | +> Les domaines en `dimdams.com` et `dimdams.net` ne peuvent pas être utilisés comme domaine personnalisé : ce sont les adresses de Dimdam's et de ses clients. | |
Ajoutécontent/hebergement/bases-de-donnees.md+39−0
| @@ -0,0 +1,39 @@ | ||
| 1 | +--- | |
| 2 | +title: Bases de données | |
| 3 | +description: MySQL, PostgreSQL, MongoDB et Redis gérés par Dimdam's. | |
| 4 | +order: 7 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Les bases de données Dimdam's tournent sur des serveurs dédiés, en France. Vous n'avez ni serveur à installer ni mise à jour à faire. | |
| 8 | + | |
| 9 | +## Se connecter | |
| 10 | + | |
| 11 | +Les identifiants (nom de la base, utilisateur, mot de passe) sont affichés sur la page du service. L'hôte est `db.dimdams.com`, avec le port habituel du moteur : | |
| 12 | + | |
| 13 | +| Moteur | Port | | |
| 14 | +| --- | --- | | |
| 15 | +| MySQL | `3306` | | |
| 16 | +| PostgreSQL | `5432` | | |
| 17 | +| MongoDB | `27017` | | |
| 18 | +| Redis | `6379` | | |
| 19 | + | |
| 20 | +Par exemple, en PostgreSQL : | |
| 21 | + | |
| 22 | +```bash | |
| 23 | +psql "postgresql://UTILISATEUR:MOT_DE_PASSE@db.dimdams.com:5432/NOM_DE_LA_BASE" | |
| 24 | +``` | |
| 25 | + | |
| 26 | +## Pare-feu | |
| 27 | + | |
| 28 | +Le pare-feu de la base n'accepte que les adresses IP que vous autorisez. Ajoutez celles de vos serveurs de production, ou la vôtre pour administrer la base. | |
| 29 | + | |
| 30 | +## Utilisateurs | |
| 31 | + | |
| 32 | +Vous pouvez créer des utilisateurs supplémentaires, par exemple un accès en lecture seule pour un outil d'analyse. | |
| 33 | + | |
| 34 | +## Export et import | |
| 35 | + | |
| 36 | +- **Exporter** télécharge un fichier `.sql` de toute la base. | |
| 37 | +- **Importer** restaure ou migre une base depuis un fichier `.sql`, sans terminal. | |
| 38 | + | |
| 39 | +Pensez aussi aux [sauvegardes](/hebergement/sauvegardes/) régulières. | |
Ajoutécontent/hebergement/bots.md+26−0
| @@ -0,0 +1,26 @@ | ||
| 1 | +--- | |
| 2 | +title: Bots | |
| 3 | +description: Héberger un bot Discord ou tout programme qui tourne en continu. | |
| 4 | +order: 10 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Un service **Bot** fait tourner votre programme en continu : bot Discord, worker, tâche planifiée… | |
| 8 | + | |
| 9 | +## Langages | |
| 10 | + | |
| 11 | +Node.js, Python, Java, Go et Rust. Choisissez le langage et sa version lors de la [configuration](/hebergement/configurer/). | |
| 12 | + | |
| 13 | +## Mettre votre code en ligne | |
| 14 | + | |
| 15 | +- par l'[explorateur de fichiers](/hebergement/fichiers/) du tableau de bord ; | |
| 16 | +- ou par le [déploiement Git](/hebergement/deploiement-git/), pour une mise à jour automatique à chaque `git push`. | |
| 17 | + | |
| 18 | +Les variables d'environnement (jeton du bot, clés d'API…) se règlent dans les paramètres du service : ne les écrivez pas dans votre code. | |
| 19 | + | |
| 20 | +## Console | |
| 21 | + | |
| 22 | +La console affiche la sortie de votre programme en direct et permet de lui envoyer des commandes. | |
| 23 | + | |
| 24 | +## Être prévenu en cas d'arrêt | |
| 25 | + | |
| 26 | +Créez une [alerte](/api/alertes/) sur l'état du service, avec un webhook Discord : vous recevez un message dans votre salon dès que le bot s'arrête. | |
Ajoutécontent/hebergement/commander.md+20−0
| @@ -0,0 +1,20 @@ | ||
| 1 | +--- | |
| 2 | +title: Commander un service | |
| 3 | +description: Choisir un service dans la boutique, régler ses ressources et payer. | |
| 4 | +order: 1 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Choisir le service | |
| 8 | + | |
| 9 | +Rendez-vous dans la [boutique]({{MAIN_SITE_URL}}/shop) et choisissez le type de service : hébergement web, bot, VPS, serveur de jeu, base de données… La liste complète est sur la page [Hébergement](/hebergement/). | |
| 10 | + | |
| 11 | +## Régler les ressources | |
| 12 | + | |
| 13 | +Ajustez la mémoire (RAM), le nombre de cœurs de processeur et l'espace disque. Le prix mensuel se met à jour au fur et à mesure. Ces ressources sont réservées à votre service : elles ne sont pas partagées avec les autres clients. | |
| 14 | + | |
| 15 | +> [!TIP] | |
| 16 | +> Commencez petit : vous pourrez augmenter les ressources plus tard depuis le tableau de bord, sans réinstaller le service. Seule la différence de prix pour les jours restants est facturée (voir [Facturation](/hebergement/facturation/)). | |
| 17 | + | |
| 18 | +## Payer | |
| 19 | + | |
| 20 | +Le paiement passe par Stripe (carte bancaire et les moyens proposés au moment du paiement). Une fois le paiement validé, le service apparaît dans votre tableau de bord, prêt à être [configuré](/hebergement/configurer/). | |
Ajoutécontent/hebergement/configurer.md+19−0
| @@ -0,0 +1,19 @@ | ||
| 1 | +--- | |
| 2 | +title: Configurer un service | |
| 3 | +description: La première configuration après la commande : nom, logiciel et version. | |
| 4 | +order: 2 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Un service qui vient d'être commandé apparaît dans le tableau de bord avec l'état « À configurer ». Cliquez sur **Configurer** pour lancer l'assistant. | |
| 8 | + | |
| 9 | +## L'assistant | |
| 10 | + | |
| 11 | +1. **Nom :** donnez un nom à votre service, pour le retrouver facilement (par exemple `api-production`). Il sert aussi à le désigner dans la [CLI](/api/cli/). | |
| 12 | +2. **Logiciel :** choisissez ce que le service fait tourner. Pour Minecraft, par exemple : Vanilla, Paper, Spigot, Forge, Fabric ou Purpur. Pour un site web : Apache, Nginx, PHP, Node.js ou Python. Pour un VPS : la distribution Linux. | |
| 13 | +3. **Version :** la version du logiciel (Minecraft 1.21, Node.js 22…). | |
| 14 | +4. **Accès :** pour un VPS, le mot de passe `root` et, si vous le souhaitez, votre clé SSH publique. | |
| 15 | + | |
| 16 | +L'installation prend en général moins d'une minute. Vous pouvez suivre son avancement dans la console. | |
| 17 | + | |
| 18 | +> [!NOTE] | |
| 19 | +> Pour changer de logiciel plus tard, une réinitialisation complète du service est possible dans ses réglages. Elle efface ses fichiers : faites une [sauvegarde](/hebergement/sauvegardes/) avant. | |
Ajoutécontent/hebergement/deploiement-git.md+28−0
| @@ -0,0 +1,28 @@ | ||
| 1 | +--- | |
| 2 | +title: Déploiement Git | |
| 3 | +description: Mettre à jour un site ou un bot automatiquement à chaque git push. | |
| 4 | +order: 9 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Reliez un site web ou un bot à un dépôt Git : à chaque `git push` sur la branche choisie, Dimdam's récupère le code, lance votre commande de construction et redémarre le service. | |
| 8 | + | |
| 9 | +Le dépôt peut être sur [Dimdam's Git](/git/), GitHub, GitLab ou n'importe quel serveur Git. | |
| 10 | + | |
| 11 | +## Mise en place | |
| 12 | + | |
| 13 | +Dans l'onglet **Git** du service : | |
| 14 | + | |
| 15 | +1. **Dépôt :** l'adresse HTTPS ou SSH du dépôt, et la branche à déployer (par défaut `main`). | |
| 16 | +2. **Clé de déploiement :** pour un dépôt privé, copiez la clé SSH publique affichée et ajoutez-la au dépôt en lecture seule (sur Dimdam's Git ou GitHub : **Paramètres › Clés de déploiement**). | |
| 17 | +3. **Webhook :** copiez l'adresse du webhook et son secret, et ajoutez-les dans les webhooks du dépôt (événement « push »). Sur Dimdam's Git, voir [Webhooks](/git/automatisation/#webhooks). | |
| 18 | +4. **Commande de construction** (facultative), lancée après chaque récupération du code, par exemple : | |
| 19 | + | |
| 20 | +```bash | |
| 21 | +npm ci && npm run build | |
| 22 | +``` | |
| 23 | + | |
| 24 | +## Suivre les déploiements | |
| 25 | + | |
| 26 | +Chaque déploiement a son journal dans l'onglet **Git** : récupération du code, construction, redémarrage. En cas d'échec, le journal indique l'étape et l'erreur. | |
| 27 | + | |
| 28 | +Vous pouvez aussi lancer un déploiement à la main, depuis le tableau de bord, avec `dimdams deploy mon-service` (voir la [CLI](/api/cli/)) ou par l'[API](/api/deploiement-git/). | |
Ajoutécontent/hebergement/equipes.md+33−0
| @@ -0,0 +1,33 @@ | ||
| 1 | +--- | |
| 2 | +title: Équipes | |
| 3 | +description: Donner accès à vos services sans partager votre mot de passe. | |
| 4 | +order: 12 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Ne partagez jamais votre mot de passe : invitez vos collaborateurs dans une équipe. | |
| 8 | + | |
| 9 | +## Créer une équipe | |
| 10 | + | |
| 11 | +Créez une équipe dans votre espace client et invitez vos collaborateurs par e-mail. Chacun doit avoir son propre compte Dimdam's. | |
| 12 | + | |
| 13 | +## Rôles et permissions | |
| 14 | + | |
| 15 | +Chaque membre a un rôle, qui fixe ce qu'il peut faire sur les services de l'équipe : | |
| 16 | + | |
| 17 | +| Permission | Ce qu'elle permet | | |
| 18 | +| --- | --- | | |
| 19 | +| Lecture | Voir les services, leur état et leur consommation | | |
| 20 | +| Journaux | Lire les journaux et la console | | |
| 21 | +| Écriture | Modifier les fichiers et la configuration | | |
| 22 | +| Terminal | Utiliser la console et le terminal | | |
| 23 | +| Administration | Tout gérer, y compris les membres de l'équipe | | |
| 24 | + | |
| 25 | +Les membres d'une équipe ne voient jamais les secrets du propriétaire (mots de passe, jetons) dans la configuration des services partagés. | |
| 26 | + | |
| 27 | +## Double authentification obligatoire | |
| 28 | + | |
| 29 | +Une équipe peut exiger que tous ses membres aient activé la double authentification sur leur compte. | |
| 30 | + | |
| 31 | +## Journal d'activité | |
| 32 | + | |
| 33 | +Chaque action importante (démarrage, arrêt, sauvegarde, modification du pare-feu…) est enregistrée, avec son auteur. Voir aussi le [journal d'audit par l'API](/api/journal-audit/). | |
Ajoutécontent/hebergement/facturation.md+17−0
| @@ -0,0 +1,17 @@ | ||
| 1 | +--- | |
| 2 | +title: Facturation | |
| 3 | +description: Abonnement mensuel, changement de ressources et factures. | |
| 4 | +order: 13 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Abonnement | |
| 8 | + | |
| 9 | +Chaque service est un abonnement mensuel, sans engagement. Le paiement passe par Stripe. Vos factures sont dans la page **Facturation** de votre espace client. | |
| 10 | + | |
| 11 | +## Changer les ressources | |
| 12 | + | |
| 13 | +Vous pouvez augmenter la mémoire, le processeur ou le disque d'un service à tout moment, depuis sa page. Vous ne payez que la différence de prix, pour les jours restants du mois en cours. Le nouveau prix s'applique ensuite chaque mois. | |
| 14 | + | |
| 15 | +## Options d'un VPS | |
| 16 | + | |
| 17 | +Les ports, sous-domaines et domaines personnalisés des VPS ont leurs propres quotas. Voir [VPS](/hebergement/vps/#ouvrir-un-port). | |
Ajoutécontent/hebergement/fichiers.md+28−0
| @@ -0,0 +1,28 @@ | ||
| 1 | +--- | |
| 2 | +title: Fichiers et accès | |
| 3 | +description: L'explorateur de fichiers du tableau de bord, et l'accès SSH / SFTP des VPS. | |
| 4 | +order: 4 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## L'explorateur de fichiers | |
| 8 | + | |
| 9 | +Tous les services ont un explorateur de fichiers dans le tableau de bord : | |
| 10 | + | |
| 11 | +- **éditeur de code** avec coloration, pour modifier un `.env`, un `server.properties` ou un script sans rien installer ; | |
| 12 | +- **envoi et téléchargement** de fichiers, et téléchargement d'un dossier entier en archive ; | |
| 13 | +- création, renommage et suppression de fichiers et de dossiers. | |
| 14 | + | |
| 15 | +## SSH et SFTP pour les VPS | |
| 16 | + | |
| 17 | +Les VPS sont accessibles en SSH par la passerelle de Dimdam's, sur le port `2222` : | |
| 18 | + | |
| 19 | +```bash | |
| 20 | +ssh root.<identifiant-du-vps>@ssh.dimdams.com -p 2222 | |
| 21 | +``` | |
| 22 | + | |
| 23 | +Par exemple : `ssh root.vps-1793740523964-d3bw0r@ssh.dimdams.com -p 2222`. L'identifiant du VPS est affiché sur sa page. Remplacez `root` par un autre utilisateur du VPS pour vous connecter avec lui. | |
| 24 | + | |
| 25 | +Les transferts de fichiers passent par la même adresse, avec `scp`, `sftp` ou un client graphique comme FileZilla ou WinSCP (protocole SFTP, port `2222`). | |
| 26 | + | |
| 27 | +> [!TIP] | |
| 28 | +> Ajoutez votre clé SSH publique dans l'onglet **Sécurité** du VPS : vous vous connecterez sans mot de passe. Plus d'informations dans [VPS](/hebergement/vps/). | |
Ajoutécontent/hebergement/gerer.md+33−0
| @@ -0,0 +1,33 @@ | ||
| 1 | +--- | |
| 2 | +title: Gérer un service | |
| 3 | +description: Démarrer, arrêter, suivre la console et la consommation de vos services. | |
| 4 | +order: 3 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Chaque service a sa page dans le tableau de bord, avec son état, ses commandes et sa consommation en direct. | |
| 8 | + | |
| 9 | +## Démarrer, arrêter, redémarrer | |
| 10 | + | |
| 11 | +- **Démarrer** lance le service. | |
| 12 | +- **Redémarrer** l'arrête proprement puis le relance : utile après un changement de configuration. | |
| 13 | +- **Arrêter** l'arrête proprement. Si le service ne répond plus, un bouton **Forcer l'arrêt** apparaît. | |
| 14 | + | |
| 15 | +Ces actions sont aussi disponibles par l'[API](/api/services/) et la [CLI](/api/cli/) (`dimdams restart mon-service`). | |
| 16 | + | |
| 17 | +## Console | |
| 18 | + | |
| 19 | +La console affiche ce que le service écrit (ses journaux) en direct. Pour un serveur de jeu ou un bot, vous pouvez aussi y taper des commandes. | |
| 20 | + | |
| 21 | +## Consommation | |
| 22 | + | |
| 23 | +Les graphiques montrent l'utilisation du processeur, de la mémoire et du réseau, sur les dernières heures, 7 jours ou 30 jours. | |
| 24 | + | |
| 25 | +- **Processeur :** s'il reste longtemps proche de 100 %, le service ralentit. Envisagez plus de cœurs. | |
| 26 | +- **Mémoire :** certaines applications (Java, par exemple) réservent de la mémoire dès le démarrage sans l'utiliser entièrement. | |
| 27 | +- **Réseau :** un pic inhabituel peut signaler une attaque ou une boucle dans votre code. | |
| 28 | + | |
| 29 | +Pour être prévenu automatiquement, créez des [alertes](/api/alertes/) : sur Discord, par e-mail ou dans le tableau de bord, quand une ressource dépasse un seuil ou quand le service s'arrête. | |
| 30 | + | |
| 31 | +## Fichiers | |
| 32 | + | |
| 33 | +Voir [Fichiers et accès](/hebergement/fichiers/). | |
Ajoutécontent/hebergement/index.md+27−0
| @@ -0,0 +1,27 @@ | ||
| 1 | +--- | |
| 2 | +title: Hébergement | |
| 3 | +description: Commander, configurer et gérer vos services sur Dimdam's. | |
| 4 | +--- | |
| 5 | + | |
| 6 | +Dimdam's héberge vos projets sur ses propres serveurs, en France. Chaque service a ses ressources réservées (processeur, mémoire, disque), un tableau de bord pour le piloter, et une adresse en `dimdams.net`. | |
| 7 | + | |
| 8 | +## Les services proposés | |
| 9 | + | |
| 10 | +| Service | Pour quoi faire | | |
| 11 | +| --- | --- | | |
| 12 | +| **Hébergement web** | Sites et applications : Apache, Nginx, PHP, Node.js, Python | | |
| 13 | +| **Bots** | Bots Discord et autres programmes qui tournent en continu : Node.js, Python, Java, Go, Rust | | |
| 14 | +| **VPS** | Un serveur Linux complet avec accès `root` : Ubuntu, Debian, Fedora, Rocky Linux, AlmaLinux, CentOS Stream, Alpine, Arch Linux | | |
| 15 | +| **Serveurs de jeu** | Minecraft (Vanilla, Paper, Spigot, Forge, Fabric, Purpur), FiveM, Garry's Mod, Rust, Palworld, Counter-Strike 2, Valheim, ARK | | |
| 16 | +| **Bases de données** | MySQL, PostgreSQL, MongoDB et Redis | | |
| 17 | +| **Voix et audio** | TeamSpeak, Mumble, Lavalink | | |
| 18 | +| **Stockage S3** | Stockage d'objets compatible S3 | | |
| 19 | +| **Applications en un clic** | Des applications prêtes à l'emploi, installées pour vous | | |
| 20 | + | |
| 21 | +## Par où commencer | |
| 22 | + | |
| 23 | +1. [Commander un service](/hebergement/commander/) dans la boutique. | |
| 24 | +2. [Le configurer](/hebergement/configurer/) : nom, logiciel, version. | |
| 25 | +3. [Le gérer](/hebergement/gerer/) depuis le tableau de bord : démarrage, console, fichiers, consommation. | |
| 26 | + | |
| 27 | +Pour automatiser tout cela depuis vos scripts, voyez l'[API et la CLI](/api/). | |
Ajoutécontent/hebergement/sauvegardes.md+26−0
| @@ -0,0 +1,26 @@ | ||
| 1 | +--- | |
| 2 | +title: Sauvegardes | |
| 3 | +description: Sauvegarder un service à la demande ou automatiquement, et le restaurer. | |
| 4 | +order: 8 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Une sauvegarde contient tous les fichiers de votre service à un instant donné. Elles sont conservées sur l'infrastructure de Dimdam's, en France. | |
| 8 | + | |
| 9 | +## Sauvegarder | |
| 10 | + | |
| 11 | +- **À la demande :** bouton **Créer une sauvegarde** dans l'onglet **Sauvegardes** du service, ou `dimdams backup mon-service` avec la [CLI](/api/cli/). | |
| 12 | +- **Automatiquement :** activez la planification, chaque jour ou chaque semaine, à l'heure de votre choix. Vous choisissez aussi combien de sauvegardes garder : jusqu'à 30 quotidiennes et 12 hebdomadaires. Les plus anciennes sont supprimées au fur et à mesure. | |
| 13 | + | |
| 14 | +> [!TIP] | |
| 15 | +> Faites une sauvegarde manuelle avant toute opération risquée : mise à jour d'un plugin, changement de version, réinitialisation. | |
| 16 | + | |
| 17 | +## Restaurer | |
| 18 | + | |
| 19 | +Choisissez une sauvegarde dans la liste et cliquez sur **Restaurer**. Le service est arrêté, ses fichiers sont remplacés par ceux de la sauvegarde, puis il redémarre. | |
| 20 | + | |
| 21 | +> [!WARNING] | |
| 22 | +> La restauration remplace les fichiers actuels du service. Ce qui a été modifié depuis la sauvegarde est perdu : sauvegardez d'abord l'état actuel si vous pourriez en avoir besoin. | |
| 23 | + | |
| 24 | +## Par l'API | |
| 25 | + | |
| 26 | +Lister, créer et planifier les sauvegardes : voir [l'API des sauvegardes](/api/sauvegardes/). | |
Ajoutécontent/hebergement/serveurs-de-jeu.md+27−0
| @@ -0,0 +1,27 @@ | ||
| 1 | +--- | |
| 2 | +title: Serveurs de jeu | |
| 3 | +description: Minecraft, FiveM et les autres jeux : version, connexion des joueurs et console. | |
| 4 | +order: 11 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +## Jeux disponibles | |
| 8 | + | |
| 9 | +| Jeu | Versions et variantes | | |
| 10 | +| --- | --- | | |
| 11 | +| Minecraft | Vanilla, Paper, Spigot, Forge, Fabric, Purpur | | |
| 12 | +| FiveM | ESX, QBCore, VORP, sans framework | | |
| 13 | +| Garry's Mod, Rust, Palworld, Counter-Strike 2, Valheim, ARK | Serveur dédié du jeu | | |
| 14 | +| TeamSpeak, Mumble | Serveurs vocaux | | |
| 15 | + | |
| 16 | +## Connexion des joueurs | |
| 17 | + | |
| 18 | +L'adresse du serveur est affichée sur sa page, avec son port : par exemple `mon-serveur.play.dimdams.net:25565`. Donnez-la à vos joueurs. | |
| 19 | + | |
| 20 | +## Gérer le serveur | |
| 21 | + | |
| 22 | +- **Console :** commandes du serveur (`op`, `whitelist`, `stop`…) et journaux en direct. | |
| 23 | +- **Fichiers :** `server.properties`, plugins, mods et mondes, depuis l'[explorateur](/hebergement/fichiers/). | |
| 24 | +- **Sauvegardes :** sauvegardez le monde avant une mise à jour ou l'ajout d'un plugin. Voir [Sauvegardes](/hebergement/sauvegardes/). | |
| 25 | + | |
| 26 | +> [!TIP] | |
| 27 | +> Si le serveur manque de mémoire (lenteurs, joueurs déconnectés, erreurs `OutOfMemoryError`), augmentez sa RAM depuis le tableau de bord : seule la différence de prix est facturée. | |
Ajoutécontent/hebergement/vps.md+45−0
| @@ -0,0 +1,45 @@ | ||
| 1 | +--- | |
| 2 | +title: VPS | |
| 3 | +description: Un serveur Linux complet avec accès root : connexion SSH, ports, IPv6 et domaines. | |
| 4 | +order: 5 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Un VPS Dimdam's est un serveur Linux complet : `systemd`, vos services (`cron`, `docker`, bases de données…), et un accès `root`. Il tourne sur nos serveurs en France, avec des ressources qui lui sont réservées. | |
| 8 | + | |
| 9 | +## Se connecter | |
| 10 | + | |
| 11 | +Par SSH, via la passerelle de Dimdam's : | |
| 12 | + | |
| 13 | +```bash | |
| 14 | +ssh root.<identifiant-du-vps>@ssh.dimdams.com -p 2222 | |
| 15 | +``` | |
| 16 | + | |
| 17 | +Le **terminal web** de la page du VPS donne aussi un shell complet dans le navigateur, sans client SSH. | |
| 18 | + | |
| 19 | +Pour vous connecter sans mot de passe, ajoutez votre clé publique (RSA ou Ed25519) dans l'onglet **Sécurité** du VPS : elle est ajoutée aux `authorized_keys` de `root`. | |
| 20 | + | |
| 21 | +## Réseau | |
| 22 | + | |
| 23 | +Chaque VPS a deux accès au réseau : | |
| 24 | + | |
| 25 | +- **Une adresse IPv6 publique dédiée :** tous les ports sont joignables directement, sans rien configurer. Elle est affichée dans l'onglet **Réseau**. | |
| 26 | +- **Une adresse IPv4 partagée :** pour les visiteurs sans IPv6, vous ouvrez les ports dont vous avez besoin, ou vous passez par un sous-domaine ou un domaine personnalisé. | |
| 27 | + | |
| 28 | +### Ouvrir un port | |
| 29 | + | |
| 30 | +Dans **Réseau › Ports ouverts & redirections**, cliquez sur **Ouvrir un port** et indiquez le port sur lequel votre application écoute dans le VPS (par exemple `4000`). Dimdam's lui attribue un port public : votre application est alors joignable sur `<identifiant-du-vps>.vps.dimdams.net:<port public>`. | |
| 31 | + | |
| 32 | +Le bouton **Tester** vérifie qu'un programme écoute bien sur ce port dans le VPS. | |
| 33 | + | |
| 34 | +> [!WARNING] | |
| 35 | +> Votre application doit écouter sur toutes les interfaces (`0.0.0.0` ou `::`), pas seulement sur `127.0.0.1` / `localhost` : sinon elle n'est pas joignable depuis l'extérieur du VPS. | |
| 36 | + | |
| 37 | +| | Inclus | Au-delà | | |
| 38 | +| --- | --- | --- | | |
| 39 | +| Ports ouverts | 3 | 0,50 € par mois et par port, jusqu'à 10 | | |
| 40 | +| Sous-domaines | 1 | 1,00 € par mois et par sous-domaine, jusqu'à 3 | | |
| 41 | +| Domaines personnalisés | Sans limite | Gratuits | | |
| 42 | + | |
| 43 | +### Sous-domaine et domaine personnalisé | |
| 44 | + | |
| 45 | +Pour une application web, liez plutôt un sous-domaine (`mon-app.vps.dimdams.net`) ou votre propre domaine à un port : le trafic arrive en HTTPS, avec un certificat automatique, sans numéro de port dans l'adresse. Voir [Adresses et domaines](/hebergement/adresses-et-domaines/). | |
Ajoutécontent/status/api.md+52−0
| @@ -0,0 +1,52 @@ | ||
| 1 | +--- | |
| 2 | +title: API publique | |
| 3 | +description: Lire l'état des services de Dimdam's depuis vos outils. | |
| 4 | +order: 1 | |
| 5 | +--- | |
| 6 | + | |
| 7 | +Les données de [Dimdam's Status]({{STATUS_URL}}) sont disponibles en JSON, sans authentification. Elles sont mises à jour toutes les 15 secondes environ : inutile d'interroger plus souvent. | |
| 8 | + | |
| 9 | +Les routes prennent le nom de la page de statut (`<page>`), celui qui apparaît dans son adresse. La page principale s'appelle `dimdams`. | |
| 10 | + | |
| 11 | +## Services et incidents | |
| 12 | + | |
| 13 | +`GET /api/status-page/<page>` | |
| 14 | + | |
| 15 | +La configuration de la page : ses groupes de services, les incidents en cours et les maintenances. | |
| 16 | + | |
| 17 | +```bash | |
| 18 | +curl "{{STATUS_URL}}/api/status-page/dimdams" | |
| 19 | +``` | |
| 20 | + | |
| 21 | +## Vérifications et disponibilité | |
| 22 | + | |
| 23 | +`GET /api/status-page/heartbeat/<page>` | |
| 24 | + | |
| 25 | +Les dernières vérifications de chaque service (dans `heartbeatList`, `status` vaut `1` si le service répond, `0` sinon) et sa disponibilité sur 24 heures (`uptimeList`). | |
| 26 | + | |
| 27 | +```bash | |
| 28 | +curl "{{STATUS_URL}}/api/status-page/heartbeat/dimdams" | |
| 29 | +``` | |
| 30 | + | |
| 31 | +Extrait de réponse : | |
| 32 | + | |
| 33 | +```json | |
| 34 | +{ | |
| 35 | + "heartbeatList": { | |
| 36 | + "12": [ | |
| 37 | + { "status": 1, "time": "2026-11-04 09:41:07", "msg": "200 - OK", "ping": 48 } | |
| 38 | + ] | |
| 39 | + }, | |
| 40 | + "uptimeList": { "12_24": 0.9993 } | |
| 41 | +} | |
| 42 | +``` | |
| 43 | + | |
| 44 | +## Historique des incidents | |
| 45 | + | |
| 46 | +`GET /api/status-page/<page>/incident-history` | |
| 47 | + | |
| 48 | +Les incidents passés, les plus récents en premier. | |
| 49 | + | |
| 50 | +```bash | |
| 51 | +curl "{{STATUS_URL}}/api/status-page/dimdams/incident-history" | |
| 52 | +``` | |
Ajoutécontent/status/index.md+34−0
| @@ -0,0 +1,34 @@ | ||
| 1 | +--- | |
| 2 | +title: Dimdam's Status | |
| 3 | +description: Savoir en un coup d'œil si les services de Dimdam's fonctionnent. | |
| 4 | +--- | |
| 5 | + | |
| 6 | +[Dimdam's Status]({{STATUS_URL}}) montre en temps réel l'état des services de Dimdam's : le site et le panel, l'hébergement, Dimdam's Git, l'API… Si quelque chose ne marche pas chez vous, commencez par là. | |
| 7 | + | |
| 8 | +## Lire la page | |
| 9 | + | |
| 10 | +Chaque service est vérifié automatiquement, toutes les minutes environ, depuis nos serveurs. Pour chacun, la page affiche : | |
| 11 | + | |
| 12 | +- **son état actuel :** opérationnel, perturbé ou en panne ; | |
| 13 | +- **une barre d'historique :** une case par vérification récente, verte si elle a réussi, rouge si elle a échoué ; survolez une case pour voir l'heure et le temps de réponse ; | |
| 14 | +- **sa disponibilité :** la part des vérifications réussies sur les dernières 24 heures et les derniers 30 jours ; | |
| 15 | +- **son adresse et son certificat** quand le service est un site : un clic ouvre le site, et la pastille indique dans combien de jours son certificat HTTPS expire. | |
| 16 | + | |
| 17 | +## Couleurs de disponibilité | |
| 18 | + | |
| 19 | +| Disponibilité | Couleur | | |
| 20 | +| --- | --- | | |
| 21 | +| 99,9 % et plus | Vert | | |
| 22 | +| De 98 % à 99,9 % | Orange | | |
| 23 | +| Moins de 98 % | Rouge | | |
| 24 | + | |
| 25 | +## Incidents et maintenances | |
| 26 | + | |
| 27 | +Quand un problème est en cours, un bandeau en haut de la page l'explique, et il est mis à jour jusqu'à sa résolution. Les maintenances prévues y sont annoncées à l'avance. L'historique des incidents passés est en bas de la page, avec un calendrier. | |
| 28 | + | |
| 29 | +> [!NOTE] | |
| 30 | +> La plupart des courtes coupures viennent du déploiement d'une mise à jour : le service redémarre, puis revient en quelques secondes. Elles sont signalées comme telles dans l'historique. | |
| 31 | + | |
| 32 | +## Être prévenu | |
| 33 | + | |
| 34 | +Pour vos propres services, configurez des [alertes](/api/alertes/) dans le panel : vous êtes prévenu par e-mail ou sur Discord quand l'un d'eux s'arrête ou consomme trop. | |
Ajouténext.config.ts+13−0
| @@ -0,0 +1,13 @@ | ||
| 1 | +import type { NextConfig } from "next"; | |
| 2 | + | |
| 3 | +// Static site: `next build` writes out/ (one folder and index.html per page), served by nginx. | |
| 4 | +// Every setting comes from the .env, read at build time (src/lib/env.ts checks them). | |
| 5 | + | |
| 6 | +const nextConfig: NextConfig = { | |
| 7 | + output: "export", | |
| 8 | + trailingSlash: true, | |
| 9 | + poweredByHeader: false, | |
| 10 | + images: { unoptimized: true }, | |
| 11 | +}; | |
| 12 | + | |
| 13 | +export default nextConfig; | |
Ajoutépackage-lock.json+1648−0
| @@ -0,0 +1,1648 @@ | ||
| 1 | +{ | |
| 2 | + "name": "dimdams-docs", | |
| 3 | + "version": "1.0.0", | |
| 4 | + "lockfileVersion": 3, | |
| 5 | + "requires": true, | |
| 6 | + "packages": { | |
| 7 | + "": { | |
| 8 | + "name": "dimdams-docs", | |
| 9 | + "version": "1.0.0", | |
| 10 | + "dependencies": { | |
| 11 | + "marked": "18.1.0", | |
| 12 | + "next": "16.4.4", | |
| 13 | + "react": "19.2.3", | |
| 14 | + "react-dom": "19.2.3", | |
| 15 | + "shiki": "4.6.0" | |
| 16 | + }, | |
| 17 | + "devDependencies": { | |
| 18 | + "@types/node": "^20.19.27", | |
| 19 | + "@types/react": "^19", | |
| 20 | + "@types/react-dom": "^19", | |
| 21 | + "typescript": "^5" | |
| 22 | + } | |
| 23 | + }, | |
| 24 | + "node_modules/@emnapi/runtime": { | |
| 25 | + "version": "1.11.3", | |
| 26 | + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", | |
| 27 | + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", | |
| 28 | + "license": "MIT", | |
| 29 | + "optional": true, | |
| 30 | + "dependencies": { | |
| 31 | + "tslib": "^2.4.0" | |
| 32 | + } | |
| 33 | + }, | |
| 34 | + "node_modules/@img/colour": { | |
| 35 | + "version": "1.1.0", | |
| 36 | + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", | |
| 37 | + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", | |
| 38 | + "license": "MIT", | |
| 39 | + "optional": true, | |
| 40 | + "engines": { | |
| 41 | + "node": ">=18" | |
| 42 | + } | |
| 43 | + }, | |
| 44 | + "node_modules/@img/sharp-darwin-arm64": { | |
| 45 | + "version": "0.35.5", | |
| 46 | + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", | |
| 47 | + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", | |
| 48 | + "cpu": [ | |
| 49 | + "arm64" | |
| 50 | + ], | |
| 51 | + "license": "Apache-2.0", | |
| 52 | + "optional": true, | |
| 53 | + "os": [ | |
| 54 | + "darwin" | |
| 55 | + ], | |
| 56 | + "engines": { | |
| 57 | + "node": ">=20.9.0" | |
| 58 | + }, | |
| 59 | + "funding": { | |
| 60 | + "url": "https://opencollective.com/libvips" | |
| 61 | + }, | |
| 62 | + "optionalDependencies": { | |
| 63 | + "@img/sharp-libvips-darwin-arm64": "1.3.4" | |
| 64 | + } | |
| 65 | + }, | |
| 66 | + "node_modules/@img/sharp-darwin-x64": { | |
| 67 | + "version": "0.35.5", | |
| 68 | + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", | |
| 69 | + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", | |
| 70 | + "cpu": [ | |
| 71 | + "x64" | |
| 72 | + ], | |
| 73 | + "license": "Apache-2.0", | |
| 74 | + "optional": true, | |
| 75 | + "os": [ | |
| 76 | + "darwin" | |
| 77 | + ], | |
| 78 | + "engines": { | |
| 79 | + "node": ">=20.9.0" | |
| 80 | + }, | |
| 81 | + "funding": { | |
| 82 | + "url": "https://opencollective.com/libvips" | |
| 83 | + }, | |
| 84 | + "optionalDependencies": { | |
| 85 | + "@img/sharp-libvips-darwin-x64": "1.3.4" | |
| 86 | + } | |
| 87 | + }, | |
| 88 | + "node_modules/@img/sharp-freebsd-wasm32": { | |
| 89 | + "version": "0.35.5", | |
| 90 | + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", | |
| 91 | + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", | |
| 92 | + "license": "Apache-2.0", | |
| 93 | + "optional": true, | |
| 94 | + "os": [ | |
| 95 | + "freebsd" | |
| 96 | + ], | |
| 97 | + "dependencies": { | |
| 98 | + "@img/sharp-wasm32": "0.35.5" | |
| 99 | + }, | |
| 100 | + "engines": { | |
| 101 | + "node": ">=20.9.0" | |
| 102 | + }, | |
| 103 | + "funding": { | |
| 104 | + "url": "https://opencollective.com/libvips" | |
| 105 | + } | |
| 106 | + }, | |
| 107 | + "node_modules/@img/sharp-libvips-darwin-arm64": { | |
| 108 | + "version": "1.3.4", | |
| 109 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", | |
| 110 | + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", | |
| 111 | + "cpu": [ | |
| 112 | + "arm64" | |
| 113 | + ], | |
| 114 | + "license": "LGPL-3.0-or-later", | |
| 115 | + "optional": true, | |
| 116 | + "os": [ | |
| 117 | + "darwin" | |
| 118 | + ], | |
| 119 | + "funding": { | |
| 120 | + "url": "https://opencollective.com/libvips" | |
| 121 | + } | |
| 122 | + }, | |
| 123 | + "node_modules/@img/sharp-libvips-darwin-x64": { | |
| 124 | + "version": "1.3.4", | |
| 125 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", | |
| 126 | + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", | |
| 127 | + "cpu": [ | |
| 128 | + "x64" | |
| 129 | + ], | |
| 130 | + "license": "LGPL-3.0-or-later", | |
| 131 | + "optional": true, | |
| 132 | + "os": [ | |
| 133 | + "darwin" | |
| 134 | + ], | |
| 135 | + "funding": { | |
| 136 | + "url": "https://opencollective.com/libvips" | |
| 137 | + } | |
| 138 | + }, | |
| 139 | + "node_modules/@img/sharp-libvips-linux-arm": { | |
| 140 | + "version": "1.3.4", | |
| 141 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", | |
| 142 | + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", | |
| 143 | + "cpu": [ | |
| 144 | + "arm" | |
| 145 | + ], | |
| 146 | + "libc": [ | |
| 147 | + "glibc" | |
| 148 | + ], | |
| 149 | + "license": "LGPL-3.0-or-later", | |
| 150 | + "optional": true, | |
| 151 | + "os": [ | |
| 152 | + "linux" | |
| 153 | + ], | |
| 154 | + "funding": { | |
| 155 | + "url": "https://opencollective.com/libvips" | |
| 156 | + } | |
| 157 | + }, | |
| 158 | + "node_modules/@img/sharp-libvips-linux-arm64": { | |
| 159 | + "version": "1.3.4", | |
| 160 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", | |
| 161 | + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", | |
| 162 | + "cpu": [ | |
| 163 | + "arm64" | |
| 164 | + ], | |
| 165 | + "libc": [ | |
| 166 | + "glibc" | |
| 167 | + ], | |
| 168 | + "license": "LGPL-3.0-or-later", | |
| 169 | + "optional": true, | |
| 170 | + "os": [ | |
| 171 | + "linux" | |
| 172 | + ], | |
| 173 | + "funding": { | |
| 174 | + "url": "https://opencollective.com/libvips" | |
| 175 | + } | |
| 176 | + }, | |
| 177 | + "node_modules/@img/sharp-libvips-linux-ppc64": { | |
| 178 | + "version": "1.3.4", | |
| 179 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", | |
| 180 | + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", | |
| 181 | + "cpu": [ | |
| 182 | + "ppc64" | |
| 183 | + ], | |
| 184 | + "libc": [ | |
| 185 | + "glibc" | |
| 186 | + ], | |
| 187 | + "license": "LGPL-3.0-or-later", | |
| 188 | + "optional": true, | |
| 189 | + "os": [ | |
| 190 | + "linux" | |
| 191 | + ], | |
| 192 | + "funding": { | |
| 193 | + "url": "https://opencollective.com/libvips" | |
| 194 | + } | |
| 195 | + }, | |
| 196 | + "node_modules/@img/sharp-libvips-linux-riscv64": { | |
| 197 | + "version": "1.3.4", | |
| 198 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", | |
| 199 | + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", | |
| 200 | + "cpu": [ | |
| 201 | + "riscv64" | |
| 202 | + ], | |
| 203 | + "libc": [ | |
| 204 | + "glibc" | |
| 205 | + ], | |
| 206 | + "license": "LGPL-3.0-or-later", | |
| 207 | + "optional": true, | |
| 208 | + "os": [ | |
| 209 | + "linux" | |
| 210 | + ], | |
| 211 | + "funding": { | |
| 212 | + "url": "https://opencollective.com/libvips" | |
| 213 | + } | |
| 214 | + }, | |
| 215 | + "node_modules/@img/sharp-libvips-linux-s390x": { | |
| 216 | + "version": "1.3.4", | |
| 217 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", | |
| 218 | + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", | |
| 219 | + "cpu": [ | |
| 220 | + "s390x" | |
| 221 | + ], | |
| 222 | + "libc": [ | |
| 223 | + "glibc" | |
| 224 | + ], | |
| 225 | + "license": "LGPL-3.0-or-later", | |
| 226 | + "optional": true, | |
| 227 | + "os": [ | |
| 228 | + "linux" | |
| 229 | + ], | |
| 230 | + "funding": { | |
| 231 | + "url": "https://opencollective.com/libvips" | |
| 232 | + } | |
| 233 | + }, | |
| 234 | + "node_modules/@img/sharp-libvips-linux-x64": { | |
| 235 | + "version": "1.3.4", | |
| 236 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", | |
| 237 | + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", | |
| 238 | + "cpu": [ | |
| 239 | + "x64" | |
| 240 | + ], | |
| 241 | + "libc": [ | |
| 242 | + "glibc" | |
| 243 | + ], | |
| 244 | + "license": "LGPL-3.0-or-later", | |
| 245 | + "optional": true, | |
| 246 | + "os": [ | |
| 247 | + "linux" | |
| 248 | + ], | |
| 249 | + "funding": { | |
| 250 | + "url": "https://opencollective.com/libvips" | |
| 251 | + } | |
| 252 | + }, | |
| 253 | + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { | |
| 254 | + "version": "1.3.4", | |
| 255 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", | |
| 256 | + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", | |
| 257 | + "cpu": [ | |
| 258 | + "arm64" | |
| 259 | + ], | |
| 260 | + "libc": [ | |
| 261 | + "musl" | |
| 262 | + ], | |
| 263 | + "license": "LGPL-3.0-or-later", | |
| 264 | + "optional": true, | |
| 265 | + "os": [ | |
| 266 | + "linux" | |
| 267 | + ], | |
| 268 | + "funding": { | |
| 269 | + "url": "https://opencollective.com/libvips" | |
| 270 | + } | |
| 271 | + }, | |
| 272 | + "node_modules/@img/sharp-libvips-linuxmusl-x64": { | |
| 273 | + "version": "1.3.4", | |
| 274 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", | |
| 275 | + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", | |
| 276 | + "cpu": [ | |
| 277 | + "x64" | |
| 278 | + ], | |
| 279 | + "libc": [ | |
| 280 | + "musl" | |
| 281 | + ], | |
| 282 | + "license": "LGPL-3.0-or-later", | |
| 283 | + "optional": true, | |
| 284 | + "os": [ | |
| 285 | + "linux" | |
| 286 | + ], | |
| 287 | + "funding": { | |
| 288 | + "url": "https://opencollective.com/libvips" | |
| 289 | + } | |
| 290 | + }, | |
| 291 | + "node_modules/@img/sharp-linux-arm": { | |
| 292 | + "version": "0.35.5", | |
| 293 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", | |
| 294 | + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", | |
| 295 | + "cpu": [ | |
| 296 | + "arm" | |
| 297 | + ], | |
| 298 | + "libc": [ | |
| 299 | + "glibc" | |
| 300 | + ], | |
| 301 | + "license": "Apache-2.0", | |
| 302 | + "optional": true, | |
| 303 | + "os": [ | |
| 304 | + "linux" | |
| 305 | + ], | |
| 306 | + "engines": { | |
| 307 | + "node": ">=20.9.0" | |
| 308 | + }, | |
| 309 | + "funding": { | |
| 310 | + "url": "https://opencollective.com/libvips" | |
| 311 | + }, | |
| 312 | + "optionalDependencies": { | |
| 313 | + "@img/sharp-libvips-linux-arm": "1.3.4" | |
| 314 | + } | |
| 315 | + }, | |
| 316 | + "node_modules/@img/sharp-linux-arm64": { | |
| 317 | + "version": "0.35.5", | |
| 318 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", | |
| 319 | + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", | |
| 320 | + "cpu": [ | |
| 321 | + "arm64" | |
| 322 | + ], | |
| 323 | + "libc": [ | |
| 324 | + "glibc" | |
| 325 | + ], | |
| 326 | + "license": "Apache-2.0", | |
| 327 | + "optional": true, | |
| 328 | + "os": [ | |
| 329 | + "linux" | |
| 330 | + ], | |
| 331 | + "engines": { | |
| 332 | + "node": ">=20.9.0" | |
| 333 | + }, | |
| 334 | + "funding": { | |
| 335 | + "url": "https://opencollective.com/libvips" | |
| 336 | + }, | |
| 337 | + "optionalDependencies": { | |
| 338 | + "@img/sharp-libvips-linux-arm64": "1.3.4" | |
| 339 | + } | |
| 340 | + }, | |
| 341 | + "node_modules/@img/sharp-linux-ppc64": { | |
| 342 | + "version": "0.35.5", | |
| 343 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", | |
| 344 | + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", | |
| 345 | + "cpu": [ | |
| 346 | + "ppc64" | |
| 347 | + ], | |
| 348 | + "libc": [ | |
| 349 | + "glibc" | |
| 350 | + ], | |
| 351 | + "license": "Apache-2.0", | |
| 352 | + "optional": true, | |
| 353 | + "os": [ | |
| 354 | + "linux" | |
| 355 | + ], | |
| 356 | + "engines": { | |
| 357 | + "node": ">=20.9.0" | |
| 358 | + }, | |
| 359 | + "funding": { | |
| 360 | + "url": "https://opencollective.com/libvips" | |
| 361 | + }, | |
| 362 | + "optionalDependencies": { | |
| 363 | + "@img/sharp-libvips-linux-ppc64": "1.3.4" | |
| 364 | + } | |
| 365 | + }, | |
| 366 | + "node_modules/@img/sharp-linux-riscv64": { | |
| 367 | + "version": "0.35.5", | |
| 368 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", | |
| 369 | + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", | |
| 370 | + "cpu": [ | |
| 371 | + "riscv64" | |
| 372 | + ], | |
| 373 | + "libc": [ | |
| 374 | + "glibc" | |
| 375 | + ], | |
| 376 | + "license": "Apache-2.0", | |
| 377 | + "optional": true, | |
| 378 | + "os": [ | |
| 379 | + "linux" | |
| 380 | + ], | |
| 381 | + "engines": { | |
| 382 | + "node": ">=20.9.0" | |
| 383 | + }, | |
| 384 | + "funding": { | |
| 385 | + "url": "https://opencollective.com/libvips" | |
| 386 | + }, | |
| 387 | + "optionalDependencies": { | |
| 388 | + "@img/sharp-libvips-linux-riscv64": "1.3.4" | |
| 389 | + } | |
| 390 | + }, | |
| 391 | + "node_modules/@img/sharp-linux-s390x": { | |
| 392 | + "version": "0.35.5", | |
| 393 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", | |
| 394 | + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", | |
| 395 | + "cpu": [ | |
| 396 | + "s390x" | |
| 397 | + ], | |
| 398 | + "libc": [ | |
| 399 | + "glibc" | |
| 400 | + ], | |
| 401 | + "license": "Apache-2.0", | |
| 402 | + "optional": true, | |
| 403 | + "os": [ | |
| 404 | + "linux" | |
| 405 | + ], | |
| 406 | + "engines": { | |
| 407 | + "node": ">=20.9.0" | |
| 408 | + }, | |
| 409 | + "funding": { | |
| 410 | + "url": "https://opencollective.com/libvips" | |
| 411 | + }, | |
| 412 | + "optionalDependencies": { | |
| 413 | + "@img/sharp-libvips-linux-s390x": "1.3.4" | |
| 414 | + } | |
| 415 | + }, | |
| 416 | + "node_modules/@img/sharp-linux-x64": { | |
| 417 | + "version": "0.35.5", | |
| 418 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", | |
| 419 | + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", | |
| 420 | + "cpu": [ | |
| 421 | + "x64" | |
| 422 | + ], | |
| 423 | + "libc": [ | |
| 424 | + "glibc" | |
| 425 | + ], | |
| 426 | + "license": "Apache-2.0", | |
| 427 | + "optional": true, | |
| 428 | + "os": [ | |
| 429 | + "linux" | |
| 430 | + ], | |
| 431 | + "engines": { | |
| 432 | + "node": ">=20.9.0" | |
| 433 | + }, | |
| 434 | + "funding": { | |
| 435 | + "url": "https://opencollective.com/libvips" | |
| 436 | + }, | |
| 437 | + "optionalDependencies": { | |
| 438 | + "@img/sharp-libvips-linux-x64": "1.3.4" | |
| 439 | + } | |
| 440 | + }, | |
| 441 | + "node_modules/@img/sharp-linuxmusl-arm64": { | |
| 442 | + "version": "0.35.5", | |
| 443 | + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", | |
| 444 | + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", | |
| 445 | + "cpu": [ | |
| 446 | + "arm64" | |
| 447 | + ], | |
| 448 | + "libc": [ | |
| 449 | + "musl" | |
| 450 | + ], | |
| 451 | + "license": "Apache-2.0", | |
| 452 | + "optional": true, | |
| 453 | + "os": [ | |
| 454 | + "linux" | |
| 455 | + ], | |
| 456 | + "engines": { | |
| 457 | + "node": ">=20.9.0" | |
| 458 | + }, | |
| 459 | + "funding": { | |
| 460 | + "url": "https://opencollective.com/libvips" | |
| 461 | + }, | |
| 462 | + "optionalDependencies": { | |
| 463 | + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" | |
| 464 | + } | |
| 465 | + }, | |
| 466 | + "node_modules/@img/sharp-linuxmusl-x64": { | |
| 467 | + "version": "0.35.5", | |
| 468 | + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", | |
| 469 | + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", | |
| 470 | + "cpu": [ | |
| 471 | + "x64" | |
| 472 | + ], | |
| 473 | + "libc": [ | |
| 474 | + "musl" | |
| 475 | + ], | |
| 476 | + "license": "Apache-2.0", | |
| 477 | + "optional": true, | |
| 478 | + "os": [ | |
| 479 | + "linux" | |
| 480 | + ], | |
| 481 | + "engines": { | |
| 482 | + "node": ">=20.9.0" | |
| 483 | + }, | |
| 484 | + "funding": { | |
| 485 | + "url": "https://opencollective.com/libvips" | |
| 486 | + }, | |
| 487 | + "optionalDependencies": { | |
| 488 | + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" | |
| 489 | + } | |
| 490 | + }, | |
| 491 | + "node_modules/@img/sharp-wasm32": { | |
| 492 | + "version": "0.35.5", | |
| 493 | + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", | |
| 494 | + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", | |
| 495 | + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", | |
| 496 | + "optional": true, | |
| 497 | + "dependencies": { | |
| 498 | + "@emnapi/runtime": "^1.11.3" | |
| 499 | + }, | |
| 500 | + "engines": { | |
| 501 | + "node": ">=20.9.0" | |
| 502 | + }, | |
| 503 | + "funding": { | |
| 504 | + "url": "https://opencollective.com/libvips" | |
| 505 | + } | |
| 506 | + }, | |
| 507 | + "node_modules/@img/sharp-webcontainers-wasm32": { | |
| 508 | + "version": "0.35.5", | |
| 509 | + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", | |
| 510 | + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", | |
| 511 | + "cpu": [ | |
| 512 | + "wasm32" | |
| 513 | + ], | |
| 514 | + "license": "Apache-2.0", | |
| 515 | + "optional": true, | |
| 516 | + "dependencies": { | |
| 517 | + "@img/sharp-wasm32": "0.35.5" | |
| 518 | + }, | |
| 519 | + "engines": { | |
| 520 | + "node": ">=20.9.0" | |
| 521 | + }, | |
| 522 | + "funding": { | |
| 523 | + "url": "https://opencollective.com/libvips" | |
| 524 | + } | |
| 525 | + }, | |
| 526 | + "node_modules/@img/sharp-win32-arm64": { | |
| 527 | + "version": "0.35.5", | |
| 528 | + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", | |
| 529 | + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", | |
| 530 | + "cpu": [ | |
| 531 | + "arm64" | |
| 532 | + ], | |
| 533 | + "license": "Apache-2.0 AND LGPL-3.0-or-later", | |
| 534 | + "optional": true, | |
| 535 | + "os": [ | |
| 536 | + "win32" | |
| 537 | + ], | |
| 538 | + "engines": { | |
| 539 | + "node": ">=20.9.0" | |
| 540 | + }, | |
| 541 | + "funding": { | |
| 542 | + "url": "https://opencollective.com/libvips" | |
| 543 | + } | |
| 544 | + }, | |
| 545 | + "node_modules/@img/sharp-win32-ia32": { | |
| 546 | + "version": "0.35.5", | |
| 547 | + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", | |
| 548 | + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", | |
| 549 | + "cpu": [ | |
| 550 | + "ia32" | |
| 551 | + ], | |
| 552 | + "license": "Apache-2.0 AND LGPL-3.0-or-later", | |
| 553 | + "optional": true, | |
| 554 | + "os": [ | |
| 555 | + "win32" | |
| 556 | + ], | |
| 557 | + "engines": { | |
| 558 | + "node": "^20.9.0" | |
| 559 | + }, | |
| 560 | + "funding": { | |
| 561 | + "url": "https://opencollective.com/libvips" | |
| 562 | + } | |
| 563 | + }, | |
| 564 | + "node_modules/@img/sharp-win32-x64": { | |
| 565 | + "version": "0.35.5", | |
| 566 | + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", | |
| 567 | + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", | |
| 568 | + "cpu": [ | |
| 569 | + "x64" | |
| 570 | + ], | |
| 571 | + "license": "Apache-2.0 AND LGPL-3.0-or-later", | |
| 572 | + "optional": true, | |
| 573 | + "os": [ | |
| 574 | + "win32" | |
| 575 | + ], | |
| 576 | + "engines": { | |
| 577 | + "node": ">=20.9.0" | |
| 578 | + }, | |
| 579 | + "funding": { | |
| 580 | + "url": "https://opencollective.com/libvips" | |
| 581 | + } | |
| 582 | + }, | |
| 583 | + "node_modules/@next/env": { | |
| 584 | + "version": "16.4.4", | |
| 585 | + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.4.4.tgz", | |
| 586 | + "integrity": "sha512-6/3ZuELR0tPLgNigvv8d/LAmfLIo1IpNfvrI/AHCAl9w9D0DNVGQGvo+OLPzAWR+WMW3vVyO7HaW0kfL97huBg==", | |
| 587 | + "license": "MIT" | |
| 588 | + }, | |
| 589 | + "node_modules/@next/swc-darwin-arm64": { | |
| 590 | + "version": "16.4.4", | |
| 591 | + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.4.4.tgz", | |
| 592 | + "integrity": "sha512-1KlZ5IJ7zcLBZvqG1q7nuRWm2LAAN4C3iBqqoBCRRl1UEv0A4k6Rek+x3j23UiWf8Gz+VsN+M/LylAWkxpm2mg==", | |
| 593 | + "cpu": [ | |
| 594 | + "arm64" | |
| 595 | + ], | |
| 596 | + "license": "MIT", | |
| 597 | + "optional": true, | |
| 598 | + "os": [ | |
| 599 | + "darwin" | |
| 600 | + ], | |
| 601 | + "engines": { | |
| 602 | + "node": ">= 10" | |
| 603 | + } | |
| 604 | + }, | |
| 605 | + "node_modules/@next/swc-darwin-x64": { | |
| 606 | + "version": "16.4.4", | |
| 607 | + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.4.4.tgz", | |
| 608 | + "integrity": "sha512-Tb7efFdhbS/ph74RS7wob84gXGM58qxoO/WrkiMsTmy/JZktKOKBeiM45W0E/b8JfBzZJdexQqSmvedBwDK0PA==", | |
| 609 | + "cpu": [ | |
| 610 | + "x64" | |
| 611 | + ], | |
| 612 | + "license": "MIT", | |
| 613 | + "optional": true, | |
| 614 | + "os": [ | |
| 615 | + "darwin" | |
| 616 | + ], | |
| 617 | + "engines": { | |
| 618 | + "node": ">= 10" | |
| 619 | + } | |
| 620 | + }, | |
| 621 | + "node_modules/@next/swc-linux-arm64-gnu": { | |
| 622 | + "version": "16.4.4", | |
| 623 | + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.4.4.tgz", | |
| 624 | + "integrity": "sha512-wojSfLnH8RRImhtbcn3Dlx9JICh6aQHgDA33XDr+EPua2tt7Q/yjDn0FTAbqfNlLrBmOASAGhLh6NvBk9L3gzw==", | |
| 625 | + "cpu": [ | |
| 626 | + "arm64" | |
| 627 | + ], | |
| 628 | + "libc": [ | |
| 629 | + "glibc" | |
| 630 | + ], | |
| 631 | + "license": "MIT", | |
| 632 | + "optional": true, | |
| 633 | + "os": [ | |
| 634 | + "linux" | |
| 635 | + ], | |
| 636 | + "engines": { | |
| 637 | + "node": ">= 10" | |
| 638 | + } | |
| 639 | + }, | |
| 640 | + "node_modules/@next/swc-linux-arm64-musl": { | |
| 641 | + "version": "16.4.4", | |
| 642 | + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.4.4.tgz", | |
| 643 | + "integrity": "sha512-zgxpGOZaR+2O6dOk5ZAUkKgH92ZtqNg3ohzyg2HmWc2N+AZH4dEpTwdpB0e5MR9YRVdVDUkEM5g6c3TWWn8g1g==", | |
| 644 | + "cpu": [ | |
| 645 | + "arm64" | |
| 646 | + ], | |
| 647 | + "libc": [ | |
| 648 | + "musl" | |
| 649 | + ], | |
| 650 | + "license": "MIT", | |
| 651 | + "optional": true, | |
| 652 | + "os": [ | |
| 653 | + "linux" | |
| 654 | + ], | |
| 655 | + "engines": { | |
| 656 | + "node": ">= 10" | |
| 657 | + } | |
| 658 | + }, | |
| 659 | + "node_modules/@next/swc-linux-x64-gnu": { | |
| 660 | + "version": "16.4.4", | |
| 661 | + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.4.4.tgz", | |
| 662 | + "integrity": "sha512-fg3Wr030Y3BDkCXi6HGsPz0BUPTGZJS8TZSrCO0tS3O/KBsCyx4eP6aeaizCpGmcUKhtb4l7JoMc88QO2qBl2g==", | |
| 663 | + "cpu": [ | |
| 664 | + "x64" | |
| 665 | + ], | |
| 666 | + "libc": [ | |
| 667 | + "glibc" | |
| 668 | + ], | |
| 669 | + "license": "MIT", | |
| 670 | + "optional": true, | |
| 671 | + "os": [ | |
| 672 | + "linux" | |
| 673 | + ], | |
| 674 | + "engines": { | |
| 675 | + "node": ">= 10" | |
| 676 | + } | |
| 677 | + }, | |
| 678 | + "node_modules/@next/swc-linux-x64-musl": { | |
| 679 | + "version": "16.4.4", | |
| 680 | + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.4.4.tgz", | |
| 681 | + "integrity": "sha512-40cr71PSdCV9zU2g0B1XG27af10tYU+9g8F+FtF3qSguQg0jSZDoxn0NrAvgFQ0cy9s0ZBA/00P9g/XL1O43xg==", | |
| 682 | + "cpu": [ | |
| 683 | + "x64" | |
| 684 | + ], | |
| 685 | + "libc": [ | |
| 686 | + "musl" | |
| 687 | + ], | |
| 688 | + "license": "MIT", | |
| 689 | + "optional": true, | |
| 690 | + "os": [ | |
| 691 | + "linux" | |
| 692 | + ], | |
| 693 | + "engines": { | |
| 694 | + "node": ">= 10" | |
| 695 | + } | |
| 696 | + }, | |
| 697 | + "node_modules/@next/swc-win32-arm64-msvc": { | |
| 698 | + "version": "16.4.4", | |
| 699 | + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.4.4.tgz", | |
| 700 | + "integrity": "sha512-vFMiEbRZ+LYNeswslf4LuGuoPHE8JbtqV0GnmzbpnGNMs0uWaR2q98hu8M/UWGycNJERBXIUss+BGd3PIJ8VZg==", | |
| 701 | + "cpu": [ | |
| 702 | + "arm64" | |
| 703 | + ], | |
| 704 | + "license": "MIT", | |
| 705 | + "optional": true, | |
| 706 | + "os": [ | |
| 707 | + "win32" | |
| 708 | + ], | |
| 709 | + "engines": { | |
| 710 | + "node": ">= 10" | |
| 711 | + } | |
| 712 | + }, | |
| 713 | + "node_modules/@next/swc-win32-x64-msvc": { | |
| 714 | + "version": "16.4.4", | |
| 715 | + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.4.4.tgz", | |
| 716 | + "integrity": "sha512-jy2P4V8BDZ+CFCzYABHYRZ1CmyFhfmYe22i8KA4uEafdYPrUjJ3Ot+lx265mLWLHiZRIfBzCn3V1F17m0vzl9Q==", | |
| 717 | + "cpu": [ | |
| 718 | + "x64" | |
| 719 | + ], | |
| 720 | + "license": "MIT", | |
| 721 | + "optional": true, | |
| 722 | + "os": [ | |
| 723 | + "win32" | |
| 724 | + ], | |
| 725 | + "engines": { | |
| 726 | + "node": ">= 10" | |
| 727 | + } | |
| 728 | + }, | |
| 729 | + "node_modules/@shikijs/core": { | |
| 730 | + "version": "4.6.0", | |
| 731 | + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.6.0.tgz", | |
| 732 | + "integrity": "sha512-bfd4OKgvoyXYiYmMRuleA656P4V7M/Du9lcTl38Ngm6KszMj7bq4J1IGZwSYR4c95Tv5UQALtYNWUwaI9UT3xw==", | |
| 733 | + "license": "MIT", | |
| 734 | + "dependencies": { | |
| 735 | + "@shikijs/primitive": "4.6.0", | |
| 736 | + "@shikijs/types": "4.6.0", | |
| 737 | + "@shikijs/vscode-textmate": "^10.1.0", | |
| 738 | + "@types/hast": "^3.0.5", | |
| 739 | + "hast-util-to-html": "^9.0.6" | |
| 740 | + }, | |
| 741 | + "engines": { | |
| 742 | + "node": ">=20" | |
| 743 | + } | |
| 744 | + }, | |
| 745 | + "node_modules/@shikijs/engine-javascript": { | |
| 746 | + "version": "4.6.0", | |
| 747 | + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.6.0.tgz", | |
| 748 | + "integrity": "sha512-K0oqxFoL+kEMdz89mHLFO15Tsbv3kFZ6WR1cXZKMvLSc+oqaM00A6pyga1qSeXB22dP0tTiSApRcGBaKWsEccg==", | |
| 749 | + "license": "MIT", | |
| 750 | + "dependencies": { | |
| 751 | + "@shikijs/types": "4.6.0", | |
| 752 | + "@shikijs/vscode-textmate": "^10.1.0", | |
| 753 | + "oniguruma-to-es": "^4.3.6" | |
| 754 | + }, | |
| 755 | + "engines": { | |
| 756 | + "node": ">=20" | |
| 757 | + } | |
| 758 | + }, | |
| 759 | + "node_modules/@shikijs/engine-oniguruma": { | |
| 760 | + "version": "4.6.0", | |
| 761 | + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.6.0.tgz", | |
| 762 | + "integrity": "sha512-aggd6Q7SuJOXNnsUbfG2XesxBJKBMAw/PDTUo9PyRjMaaJJ2dNrI+q0C+25ARXAK9yLHAlwxc9oRT2u68YKZ7w==", | |
| 763 | + "license": "MIT", | |
| 764 | + "dependencies": { | |
| 765 | + "@shikijs/types": "4.6.0", | |
| 766 | + "@shikijs/vscode-textmate": "^10.1.0" | |
| 767 | + }, | |
| 768 | + "engines": { | |
| 769 | + "node": ">=20" | |
| 770 | + } | |
| 771 | + }, | |
| 772 | + "node_modules/@shikijs/langs": { | |
| 773 | + "version": "4.6.0", | |
| 774 | + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.6.0.tgz", | |
| 775 | + "integrity": "sha512-1yMEX3oCVV54LUq/ovfpRkDQoYDkcvjSWFFa/YLUDMiSoRzMi2uxLJYbv41ahT8Lu9lFMaa7hecd3Q39trqUyg==", | |
| 776 | + "license": "MIT", | |
| 777 | + "dependencies": { | |
| 778 | + "@shikijs/types": "4.6.0" | |
| 779 | + }, | |
| 780 | + "engines": { | |
| 781 | + "node": ">=20" | |
| 782 | + } | |
| 783 | + }, | |
| 784 | + "node_modules/@shikijs/primitive": { | |
| 785 | + "version": "4.6.0", | |
| 786 | + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.6.0.tgz", | |
| 787 | + "integrity": "sha512-c54+TQEo9ToqgfL/AU8FpjnV09+GA9Oa7M5ROkkXteZfp5XzpAJGXjjvZQFQioJOwm/w/6Z3lIzQstAkl8coag==", | |
| 788 | + "license": "MIT", | |
| 789 | + "dependencies": { | |
| 790 | + "@shikijs/types": "4.6.0", | |
| 791 | + "@shikijs/vscode-textmate": "^10.1.0", | |
| 792 | + "@types/hast": "^3.0.5" | |
| 793 | + }, | |
| 794 | + "engines": { | |
| 795 | + "node": ">=20" | |
| 796 | + } | |
| 797 | + }, | |
| 798 | + "node_modules/@shikijs/themes": { | |
| 799 | + "version": "4.6.0", | |
| 800 | + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.6.0.tgz", | |
| 801 | + "integrity": "sha512-qHAVAs80RiGD+IRuH9G+JPDOEc9A/kHsng4EzCUCwB9bLzW6Z0y9WETh2YyCcADJBmfrIaJ1Jp7KBNP6FHkgtQ==", | |
| 802 | + "license": "MIT", | |
| 803 | + "dependencies": { | |
| 804 | + "@shikijs/types": "4.6.0" | |
| 805 | + }, | |
| 806 | + "engines": { | |
| 807 | + "node": ">=20" | |
| 808 | + } | |
| 809 | + }, | |
| 810 | + "node_modules/@shikijs/types": { | |
| 811 | + "version": "4.6.0", | |
| 812 | + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.6.0.tgz", | |
| 813 | + "integrity": "sha512-+ENG1adsKt/X7cRYw1Nl7BEpBmPOjmQ7bR/eGmHuMlT8gtOFf3KEi5l31DJYyAWhW6Y6bjP1plU7FmaxWGENFw==", | |
| 814 | + "license": "MIT", | |
| 815 | + "dependencies": { | |
| 816 | + "@shikijs/vscode-textmate": "^10.1.0", | |
| 817 | + "@types/hast": "^3.0.5" | |
| 818 | + }, | |
| 819 | + "engines": { | |
| 820 | + "node": ">=20" | |
| 821 | + } | |
| 822 | + }, | |
| 823 | + "node_modules/@shikijs/vscode-textmate": { | |
| 824 | + "version": "10.1.0", | |
| 825 | + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.1.0.tgz", | |
| 826 | + "integrity": "sha512-cVBuhDQjBZaNMTwBquYfPydrUP+E3hE6oYGasx8h6ZrODIM7/r+gxLDXdd66oszbrggZHEntdE6j+5gYIVyMWQ==", | |
| 827 | + "license": "MIT" | |
| 828 | + }, | |
| 829 | + "node_modules/@swc/helpers": { | |
| 830 | + "version": "0.5.23", | |
| 831 | + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", | |
| 832 | + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", | |
| 833 | + "license": "Apache-2.0", | |
| 834 | + "dependencies": { | |
| 835 | + "tslib": "^2.8.0" | |
| 836 | + } | |
| 837 | + }, | |
| 838 | + "node_modules/@types/hast": { | |
| 839 | + "version": "3.0.5", | |
| 840 | + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", | |
| 841 | + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", | |
| 842 | + "license": "MIT", | |
| 843 | + "dependencies": { | |
| 844 | + "@types/unist": "*" | |
| 845 | + } | |
| 846 | + }, | |
| 847 | + "node_modules/@types/mdast": { | |
| 848 | + "version": "4.0.4", | |
| 849 | + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", | |
| 850 | + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", | |
| 851 | + "license": "MIT", | |
| 852 | + "dependencies": { | |
| 853 | + "@types/unist": "*" | |
| 854 | + } | |
| 855 | + }, | |
| 856 | + "node_modules/@types/node": { | |
| 857 | + "version": "20.19.43", | |
| 858 | + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", | |
| 859 | + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", | |
| 860 | + "dev": true, | |
| 861 | + "license": "MIT", | |
| 862 | + "dependencies": { | |
| 863 | + "undici-types": "~6.21.0" | |
| 864 | + } | |
| 865 | + }, | |
| 866 | + "node_modules/@types/react": { | |
| 867 | + "version": "19.3.0", | |
| 868 | + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", | |
| 869 | + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", | |
| 870 | + "dev": true, | |
| 871 | + "license": "MIT", | |
| 872 | + "dependencies": { | |
| 873 | + "csstype": "^3.2.2" | |
| 874 | + } | |
| 875 | + }, | |
| 876 | + "node_modules/@types/react-dom": { | |
| 877 | + "version": "19.3.0", | |
| 878 | + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", | |
| 879 | + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", | |
| 880 | + "dev": true, | |
| 881 | + "license": "MIT", | |
| 882 | + "peerDependencies": { | |
| 883 | + "@types/react": "^19.3.0" | |
| 884 | + } | |
| 885 | + }, | |
| 886 | + "node_modules/@types/unist": { | |
| 887 | + "version": "3.0.3", | |
| 888 | + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", | |
| 889 | + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", | |
| 890 | + "license": "MIT" | |
| 891 | + }, | |
| 892 | + "node_modules/@ungap/structured-clone": { | |
| 893 | + "version": "1.4.0", | |
| 894 | + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", | |
| 895 | + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", | |
| 896 | + "license": "ISC" | |
| 897 | + }, | |
| 898 | + "node_modules/baseline-browser-mapping": { | |
| 899 | + "version": "2.11.35", | |
| 900 | + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.35.tgz", | |
| 901 | + "integrity": "sha512-GNk8KX52CHqeNKGk8+7tZnQab/2k1t/BgrUHPKNdijzpEhF1saPQZHVKb6m5loSAxB39gyrTYw34Qf9CQ3u1qQ==", | |
| 902 | + "license": "Apache-2.0", | |
| 903 | + "bin": { | |
| 904 | + "baseline-browser-mapping": "dist/cli.cjs" | |
| 905 | + }, | |
| 906 | + "engines": { | |
| 907 | + "node": ">=6.0.0" | |
| 908 | + } | |
| 909 | + }, | |
| 910 | + "node_modules/caniuse-lite": { | |
| 911 | + "version": "1.0.30001820", | |
| 912 | + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001820.tgz", | |
| 913 | + "integrity": "sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w==", | |
| 914 | + "funding": [ | |
| 915 | + { | |
| 916 | + "type": "opencollective", | |
| 917 | + "url": "https://opencollective.com/browserslist" | |
| 918 | + }, | |
| 919 | + { | |
| 920 | + "type": "tidelift", | |
| 921 | + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" | |
| 922 | + }, | |
| 923 | + { | |
| 924 | + "type": "github", | |
| 925 | + "url": "https://github.com/sponsors/ai" | |
| 926 | + } | |
| 927 | + ], | |
| 928 | + "license": "CC-BY-4.0" | |
| 929 | + }, | |
| 930 | + "node_modules/ccount": { | |
| 931 | + "version": "2.0.1", | |
| 932 | + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", | |
| 933 | + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", | |
| 934 | + "license": "MIT", | |
| 935 | + "funding": { | |
| 936 | + "type": "github", | |
| 937 | + "url": "https://github.com/sponsors/wooorm" | |
| 938 | + } | |
| 939 | + }, | |
| 940 | + "node_modules/character-entities-html4": { | |
| 941 | + "version": "2.1.0", | |
| 942 | + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", | |
| 943 | + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", | |
| 944 | + "license": "MIT", | |
| 945 | + "funding": { | |
| 946 | + "type": "github", | |
| 947 | + "url": "https://github.com/sponsors/wooorm" | |
| 948 | + } | |
| 949 | + }, | |
| 950 | + "node_modules/character-entities-legacy": { | |
| 951 | + "version": "3.0.0", | |
| 952 | + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", | |
| 953 | + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", | |
| 954 | + "license": "MIT", | |
| 955 | + "funding": { | |
| 956 | + "type": "github", | |
| 957 | + "url": "https://github.com/sponsors/wooorm" | |
| 958 | + } | |
| 959 | + }, | |
| 960 | + "node_modules/client-only": { | |
| 961 | + "version": "0.0.1", | |
| 962 | + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", | |
| 963 | + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", | |
| 964 | + "license": "MIT" | |
| 965 | + }, | |
| 966 | + "node_modules/comma-separated-tokens": { | |
| 967 | + "version": "2.0.3", | |
| 968 | + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", | |
| 969 | + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", | |
| 970 | + "license": "MIT", | |
| 971 | + "funding": { | |
| 972 | + "type": "github", | |
| 973 | + "url": "https://github.com/sponsors/wooorm" | |
| 974 | + } | |
| 975 | + }, | |
| 976 | + "node_modules/csstype": { | |
| 977 | + "version": "3.2.3", | |
| 978 | + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", | |
| 979 | + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", | |
| 980 | + "dev": true, | |
| 981 | + "license": "MIT" | |
| 982 | + }, | |
| 983 | + "node_modules/dequal": { | |
| 984 | + "version": "2.0.3", | |
| 985 | + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", | |
| 986 | + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", | |
| 987 | + "license": "MIT", | |
| 988 | + "engines": { | |
| 989 | + "node": ">=6" | |
| 990 | + } | |
| 991 | + }, | |
| 992 | + "node_modules/detect-libc": { | |
| 993 | + "version": "2.1.2", | |
| 994 | + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", | |
| 995 | + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", | |
| 996 | + "license": "Apache-2.0", | |
| 997 | + "optional": true, | |
| 998 | + "engines": { | |
| 999 | + "node": ">=8" | |
| 1000 | + } | |
| 1001 | + }, | |
| 1002 | + "node_modules/devlop": { | |
| 1003 | + "version": "1.1.0", | |
| 1004 | + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", | |
| 1005 | + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", | |
| 1006 | + "license": "MIT", | |
| 1007 | + "dependencies": { | |
| 1008 | + "dequal": "^2.0.0" | |
| 1009 | + }, | |
| 1010 | + "funding": { | |
| 1011 | + "type": "github", | |
| 1012 | + "url": "https://github.com/sponsors/wooorm" | |
| 1013 | + } | |
| 1014 | + }, | |
| 1015 | + "node_modules/hast-util-to-html": { | |
| 1016 | + "version": "9.0.6", | |
| 1017 | + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.6.tgz", | |
| 1018 | + "integrity": "sha512-1SZUxCN3TtJAE7e2BQN2MBHsoZ1gnmi9GoCP5Di59CSWptIezSAW09yDZyVbanb78V9eCwrKqdQsjtfVTxYHWA==", | |
| 1019 | + "license": "MIT", | |
| 1020 | + "dependencies": { | |
| 1021 | + "@types/hast": "^3.0.0", | |
| 1022 | + "@types/unist": "^3.0.0", | |
| 1023 | + "ccount": "^2.0.0", | |
| 1024 | + "comma-separated-tokens": "^2.0.0", | |
| 1025 | + "hast-util-whitespace": "^3.0.0", | |
| 1026 | + "html-void-elements": "^3.0.0", | |
| 1027 | + "mdast-util-to-hast": "^13.0.0", | |
| 1028 | + "property-information": "^7.0.0", | |
| 1029 | + "space-separated-tokens": "^2.0.0", | |
| 1030 | + "stringify-entities": "^4.0.0", | |
| 1031 | + "zwitch": "^2.0.4" | |
| 1032 | + }, | |
| 1033 | + "funding": { | |
| 1034 | + "type": "opencollective", | |
| 1035 | + "url": "https://opencollective.com/unified" | |
| 1036 | + } | |
| 1037 | + }, | |
| 1038 | + "node_modules/hast-util-whitespace": { | |
| 1039 | + "version": "3.0.0", | |
| 1040 | + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", | |
| 1041 | + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", | |
| 1042 | + "license": "MIT", | |
| 1043 | + "dependencies": { | |
| 1044 | + "@types/hast": "^3.0.0" | |
| 1045 | + }, | |
| 1046 | + "funding": { | |
| 1047 | + "type": "opencollective", | |
| 1048 | + "url": "https://opencollective.com/unified" | |
| 1049 | + } | |
| 1050 | + }, | |
| 1051 | + "node_modules/html-void-elements": { | |
| 1052 | + "version": "3.0.0", | |
| 1053 | + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", | |
| 1054 | + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", | |
| 1055 | + "license": "MIT", | |
| 1056 | + "funding": { | |
| 1057 | + "type": "github", | |
| 1058 | + "url": "https://github.com/sponsors/wooorm" | |
| 1059 | + } | |
| 1060 | + }, | |
| 1061 | + "node_modules/marked": { | |
| 1062 | + "version": "18.1.0", | |
| 1063 | + "resolved": "https://registry.npmjs.org/marked/-/marked-18.1.0.tgz", | |
| 1064 | + "integrity": "sha512-PamYXWWWg2nboG3oX5Ffzpy3EFZROqZJK9hSlMmUnekp7TxPs5DcT95vwd46m73/exfKCRVMtgXoPMI4FEJL6w==", | |
| 1065 | + "license": "MIT", | |
| 1066 | + "bin": { | |
| 1067 | + "marked": "bin/marked.js" | |
| 1068 | + }, | |
| 1069 | + "engines": { | |
| 1070 | + "node": ">= 20" | |
| 1071 | + } | |
| 1072 | + }, | |
| 1073 | + "node_modules/mdast-util-to-hast": { | |
| 1074 | + "version": "13.2.1", | |
| 1075 | + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", | |
| 1076 | + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", | |
| 1077 | + "license": "MIT", | |
| 1078 | + "dependencies": { | |
| 1079 | + "@types/hast": "^3.0.0", | |
| 1080 | + "@types/mdast": "^4.0.0", | |
| 1081 | + "@ungap/structured-clone": "^1.0.0", | |
| 1082 | + "devlop": "^1.0.0", | |
| 1083 | + "micromark-util-sanitize-uri": "^2.0.0", | |
| 1084 | + "trim-lines": "^3.0.0", | |
| 1085 | + "unist-util-position": "^5.0.0", | |
| 1086 | + "unist-util-visit": "^5.0.0", | |
| 1087 | + "vfile": "^6.0.0" | |
| 1088 | + }, | |
| 1089 | + "funding": { | |
| 1090 | + "type": "opencollective", | |
| 1091 | + "url": "https://opencollective.com/unified" | |
| 1092 | + } | |
| 1093 | + }, | |
| 1094 | + "node_modules/micromark-util-character": { | |
| 1095 | + "version": "2.1.1", | |
| 1096 | + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", | |
| 1097 | + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", | |
| 1098 | + "funding": [ | |
| 1099 | + { | |
| 1100 | + "type": "GitHub Sponsors", | |
| 1101 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 1102 | + }, | |
| 1103 | + { | |
| 1104 | + "type": "OpenCollective", | |
| 1105 | + "url": "https://opencollective.com/unified" | |
| 1106 | + } | |
| 1107 | + ], | |
| 1108 | + "license": "MIT", | |
| 1109 | + "dependencies": { | |
| 1110 | + "micromark-util-symbol": "^2.0.0", | |
| 1111 | + "micromark-util-types": "^2.0.0" | |
| 1112 | + } | |
| 1113 | + }, | |
| 1114 | + "node_modules/micromark-util-encode": { | |
| 1115 | + "version": "2.0.1", | |
| 1116 | + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", | |
| 1117 | + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", | |
| 1118 | + "funding": [ | |
| 1119 | + { | |
| 1120 | + "type": "GitHub Sponsors", | |
| 1121 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 1122 | + }, | |
| 1123 | + { | |
| 1124 | + "type": "OpenCollective", | |
| 1125 | + "url": "https://opencollective.com/unified" | |
| 1126 | + } | |
| 1127 | + ], | |
| 1128 | + "license": "MIT" | |
| 1129 | + }, | |
| 1130 | + "node_modules/micromark-util-sanitize-uri": { | |
| 1131 | + "version": "2.0.1", | |
| 1132 | + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", | |
| 1133 | + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", | |
| 1134 | + "funding": [ | |
| 1135 | + { | |
| 1136 | + "type": "GitHub Sponsors", | |
| 1137 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 1138 | + }, | |
| 1139 | + { | |
| 1140 | + "type": "OpenCollective", | |
| 1141 | + "url": "https://opencollective.com/unified" | |
| 1142 | + } | |
| 1143 | + ], | |
| 1144 | + "license": "MIT", | |
| 1145 | + "dependencies": { | |
| 1146 | + "micromark-util-character": "^2.0.0", | |
| 1147 | + "micromark-util-encode": "^2.0.0", | |
| 1148 | + "micromark-util-symbol": "^2.0.0" | |
| 1149 | + } | |
| 1150 | + }, | |
| 1151 | + "node_modules/micromark-util-symbol": { | |
| 1152 | + "version": "2.0.1", | |
| 1153 | + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", | |
| 1154 | + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", | |
| 1155 | + "funding": [ | |
| 1156 | + { | |
| 1157 | + "type": "GitHub Sponsors", | |
| 1158 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 1159 | + }, | |
| 1160 | + { | |
| 1161 | + "type": "OpenCollective", | |
| 1162 | + "url": "https://opencollective.com/unified" | |
| 1163 | + } | |
| 1164 | + ], | |
| 1165 | + "license": "MIT" | |
| 1166 | + }, | |
| 1167 | + "node_modules/micromark-util-types": { | |
| 1168 | + "version": "2.0.3", | |
| 1169 | + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.3.tgz", | |
| 1170 | + "integrity": "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==", | |
| 1171 | + "funding": [ | |
| 1172 | + { | |
| 1173 | + "type": "GitHub Sponsors", | |
| 1174 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 1175 | + }, | |
| 1176 | + { | |
| 1177 | + "type": "OpenCollective", | |
| 1178 | + "url": "https://opencollective.com/unified" | |
| 1179 | + } | |
| 1180 | + ], | |
| 1181 | + "license": "MIT" | |
| 1182 | + }, | |
| 1183 | + "node_modules/nanoid": { | |
| 1184 | + "version": "3.3.20", | |
| 1185 | + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", | |
| 1186 | + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", | |
| 1187 | + "funding": [ | |
| 1188 | + { | |
| 1189 | + "type": "github", | |
| 1190 | + "url": "https://github.com/sponsors/ai" | |
| 1191 | + } | |
| 1192 | + ], | |
| 1193 | + "license": "MIT", | |
| 1194 | + "bin": { | |
| 1195 | + "nanoid": "bin/nanoid.cjs" | |
| 1196 | + }, | |
| 1197 | + "engines": { | |
| 1198 | + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" | |
| 1199 | + } | |
| 1200 | + }, | |
| 1201 | + "node_modules/next": { | |
| 1202 | + "version": "16.4.4", | |
| 1203 | + "resolved": "https://registry.npmjs.org/next/-/next-16.4.4.tgz", | |
| 1204 | + "integrity": "sha512-Hu4pOw2x0RQ0TQabNWbpsNg/3+SZf+rLQwvfhBLnptv98GS4dcIVMlfEyn9jb2/ZJDSahhnUKEs0YrfarjGX3A==", | |
| 1205 | + "license": "MIT", | |
| 1206 | + "dependencies": { | |
| 1207 | + "@next/env": "16.4.4", | |
| 1208 | + "@swc/helpers": "0.5.23", | |
| 1209 | + "baseline-browser-mapping": "^2.9.19", | |
| 1210 | + "caniuse-lite": "^1.0.30001579", | |
| 1211 | + "postcss": "8.5.23", | |
| 1212 | + "styled-jsx": "5.1.6" | |
| 1213 | + }, | |
| 1214 | + "bin": { | |
| 1215 | + "next": "dist/bin/next" | |
| 1216 | + }, | |
| 1217 | + "engines": { | |
| 1218 | + "node": ">=20.9.0" | |
| 1219 | + }, | |
| 1220 | + "optionalDependencies": { | |
| 1221 | + "@next/swc-darwin-arm64": "16.4.4", | |
| 1222 | + "@next/swc-darwin-x64": "16.4.4", | |
| 1223 | + "@next/swc-linux-arm64-gnu": "16.4.4", | |
| 1224 | + "@next/swc-linux-arm64-musl": "16.4.4", | |
| 1225 | + "@next/swc-linux-x64-gnu": "16.4.4", | |
| 1226 | + "@next/swc-linux-x64-musl": "16.4.4", | |
| 1227 | + "@next/swc-win32-arm64-msvc": "16.4.4", | |
| 1228 | + "@next/swc-win32-x64-msvc": "16.4.4", | |
| 1229 | + "sharp": "^0.35.5" | |
| 1230 | + }, | |
| 1231 | + "peerDependencies": { | |
| 1232 | + "@opentelemetry/api": "^1.1.0", | |
| 1233 | + "@playwright/test": "^1.51.1", | |
| 1234 | + "babel-plugin-react-compiler": "*", | |
| 1235 | + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", | |
| 1236 | + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", | |
| 1237 | + "sass": "^1.3.0" | |
| 1238 | + }, | |
| 1239 | + "peerDependenciesMeta": { | |
| 1240 | + "@opentelemetry/api": { | |
| 1241 | + "optional": true | |
| 1242 | + }, | |
| 1243 | + "@playwright/test": { | |
| 1244 | + "optional": true | |
| 1245 | + }, | |
| 1246 | + "babel-plugin-react-compiler": { | |
| 1247 | + "optional": true | |
| 1248 | + }, | |
| 1249 | + "sass": { | |
| 1250 | + "optional": true | |
| 1251 | + } | |
| 1252 | + } | |
| 1253 | + }, | |
| 1254 | + "node_modules/oniguruma-parser": { | |
| 1255 | + "version": "0.12.2", | |
| 1256 | + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", | |
| 1257 | + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", | |
| 1258 | + "license": "MIT" | |
| 1259 | + }, | |
| 1260 | + "node_modules/oniguruma-to-es": { | |
| 1261 | + "version": "4.3.6", | |
| 1262 | + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", | |
| 1263 | + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", | |
| 1264 | + "license": "MIT", | |
| 1265 | + "dependencies": { | |
| 1266 | + "oniguruma-parser": "^0.12.2", | |
| 1267 | + "regex": "^6.1.0", | |
| 1268 | + "regex-recursion": "^6.0.2" | |
| 1269 | + } | |
| 1270 | + }, | |
| 1271 | + "node_modules/picocolors": { | |
| 1272 | + "version": "1.1.1", | |
| 1273 | + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", | |
| 1274 | + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", | |
| 1275 | + "license": "ISC" | |
| 1276 | + }, | |
| 1277 | + "node_modules/postcss": { | |
| 1278 | + "version": "8.5.23", | |
| 1279 | + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", | |
| 1280 | + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", | |
| 1281 | + "funding": [ | |
| 1282 | + { | |
| 1283 | + "type": "opencollective", | |
| 1284 | + "url": "https://opencollective.com/postcss/" | |
| 1285 | + }, | |
| 1286 | + { | |
| 1287 | + "type": "tidelift", | |
| 1288 | + "url": "https://tidelift.com/funding/github/npm/postcss" | |
| 1289 | + }, | |
| 1290 | + { | |
| 1291 | + "type": "github", | |
| 1292 | + "url": "https://github.com/sponsors/ai" | |
| 1293 | + } | |
| 1294 | + ], | |
| 1295 | + "license": "MIT", | |
| 1296 | + "dependencies": { | |
| 1297 | + "nanoid": "^3.3.16", | |
| 1298 | + "picocolors": "^1.1.1", | |
| 1299 | + "source-map-js": "^1.2.1" | |
| 1300 | + }, | |
| 1301 | + "engines": { | |
| 1302 | + "node": "^10 || ^12 || >=14" | |
| 1303 | + } | |
| 1304 | + }, | |
| 1305 | + "node_modules/property-information": { | |
| 1306 | + "version": "7.2.0", | |
| 1307 | + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", | |
| 1308 | + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", | |
| 1309 | + "license": "MIT", | |
| 1310 | + "funding": { | |
| 1311 | + "type": "github", | |
| 1312 | + "url": "https://github.com/sponsors/wooorm" | |
| 1313 | + } | |
| 1314 | + }, | |
| 1315 | + "node_modules/react": { | |
| 1316 | + "version": "19.2.3", | |
| 1317 | + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", | |
| 1318 | + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", | |
| 1319 | + "license": "MIT", | |
| 1320 | + "engines": { | |
| 1321 | + "node": ">=0.10.0" | |
| 1322 | + } | |
| 1323 | + }, | |
| 1324 | + "node_modules/react-dom": { | |
| 1325 | + "version": "19.2.3", | |
| 1326 | + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", | |
| 1327 | + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", | |
| 1328 | + "license": "MIT", | |
| 1329 | + "dependencies": { | |
| 1330 | + "scheduler": "^0.27.0" | |
| 1331 | + }, | |
| 1332 | + "peerDependencies": { | |
| 1333 | + "react": "^19.2.3" | |
| 1334 | + } | |
| 1335 | + }, | |
| 1336 | + "node_modules/regex": { | |
| 1337 | + "version": "6.1.0", | |
| 1338 | + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", | |
| 1339 | + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", | |
| 1340 | + "license": "MIT", | |
| 1341 | + "dependencies": { | |
| 1342 | + "regex-utilities": "^2.3.0" | |
| 1343 | + } | |
| 1344 | + }, | |
| 1345 | + "node_modules/regex-recursion": { | |
| 1346 | + "version": "6.0.2", | |
| 1347 | + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", | |
| 1348 | + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", | |
| 1349 | + "license": "MIT", | |
| 1350 | + "dependencies": { | |
| 1351 | + "regex-utilities": "^2.3.0" | |
| 1352 | + } | |
| 1353 | + }, | |
| 1354 | + "node_modules/regex-utilities": { | |
| 1355 | + "version": "2.3.0", | |
| 1356 | + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", | |
| 1357 | + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", | |
| 1358 | + "license": "MIT" | |
| 1359 | + }, | |
| 1360 | + "node_modules/scheduler": { | |
| 1361 | + "version": "0.27.0", | |
| 1362 | + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", | |
| 1363 | + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", | |
| 1364 | + "license": "MIT" | |
| 1365 | + }, | |
| 1366 | + "node_modules/semver": { | |
| 1367 | + "version": "7.8.6", | |
| 1368 | + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.6.tgz", | |
| 1369 | + "integrity": "sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==", | |
| 1370 | + "license": "ISC", | |
| 1371 | + "optional": true, | |
| 1372 | + "bin": { | |
| 1373 | + "semver": "bin/semver.js" | |
| 1374 | + }, | |
| 1375 | + "engines": { | |
| 1376 | + "node": ">=10" | |
| 1377 | + } | |
| 1378 | + }, | |
| 1379 | + "node_modules/sharp": { | |
| 1380 | + "version": "0.35.5", | |
| 1381 | + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", | |
| 1382 | + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", | |
| 1383 | + "license": "Apache-2.0", | |
| 1384 | + "optional": true, | |
| 1385 | + "dependencies": { | |
| 1386 | + "@img/colour": "^1.1.0", | |
| 1387 | + "detect-libc": "^2.1.2", | |
| 1388 | + "semver": "^7.8.5" | |
| 1389 | + }, | |
| 1390 | + "engines": { | |
| 1391 | + "node": ">=20.9.0" | |
| 1392 | + }, | |
| 1393 | + "funding": { | |
| 1394 | + "url": "https://opencollective.com/libvips" | |
| 1395 | + }, | |
| 1396 | + "optionalDependencies": { | |
| 1397 | + "@img/sharp-darwin-arm64": "0.35.5", | |
| 1398 | + "@img/sharp-darwin-x64": "0.35.5", | |
| 1399 | + "@img/sharp-freebsd-wasm32": "0.35.5", | |
| 1400 | + "@img/sharp-libvips-darwin-arm64": "1.3.4", | |
| 1401 | + "@img/sharp-libvips-darwin-x64": "1.3.4", | |
| 1402 | + "@img/sharp-libvips-linux-arm": "1.3.4", | |
| 1403 | + "@img/sharp-libvips-linux-arm64": "1.3.4", | |
| 1404 | + "@img/sharp-libvips-linux-ppc64": "1.3.4", | |
| 1405 | + "@img/sharp-libvips-linux-riscv64": "1.3.4", | |
| 1406 | + "@img/sharp-libvips-linux-s390x": "1.3.4", | |
| 1407 | + "@img/sharp-libvips-linux-x64": "1.3.4", | |
| 1408 | + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", | |
| 1409 | + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", | |
| 1410 | + "@img/sharp-linux-arm": "0.35.5", | |
| 1411 | + "@img/sharp-linux-arm64": "0.35.5", | |
| 1412 | + "@img/sharp-linux-ppc64": "0.35.5", | |
| 1413 | + "@img/sharp-linux-riscv64": "0.35.5", | |
| 1414 | + "@img/sharp-linux-s390x": "0.35.5", | |
| 1415 | + "@img/sharp-linux-x64": "0.35.5", | |
| 1416 | + "@img/sharp-linuxmusl-arm64": "0.35.5", | |
| 1417 | + "@img/sharp-linuxmusl-x64": "0.35.5", | |
| 1418 | + "@img/sharp-webcontainers-wasm32": "0.35.5", | |
| 1419 | + "@img/sharp-win32-arm64": "0.35.5", | |
| 1420 | + "@img/sharp-win32-ia32": "0.35.5", | |
| 1421 | + "@img/sharp-win32-x64": "0.35.5" | |
| 1422 | + }, | |
| 1423 | + "peerDependenciesMeta": { | |
| 1424 | + "@types/node": { | |
| 1425 | + "optional": true | |
| 1426 | + } | |
| 1427 | + } | |
| 1428 | + }, | |
| 1429 | + "node_modules/shiki": { | |
| 1430 | + "version": "4.6.0", | |
| 1431 | + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.6.0.tgz", | |
| 1432 | + "integrity": "sha512-imjLsVHgPuWhvpU3P+zOekvMYqnel7QmKsFC4Sn0fCuAvaaalBsxwKEsrU8Ko5BdFnZA6Go/8a0RzahS634UgA==", | |
| 1433 | + "license": "MIT", | |
| 1434 | + "dependencies": { | |
| 1435 | + "@shikijs/core": "4.6.0", | |
| 1436 | + "@shikijs/engine-javascript": "4.6.0", | |
| 1437 | + "@shikijs/engine-oniguruma": "4.6.0", | |
| 1438 | + "@shikijs/langs": "4.6.0", | |
| 1439 | + "@shikijs/themes": "4.6.0", | |
| 1440 | + "@shikijs/types": "4.6.0", | |
| 1441 | + "@shikijs/vscode-textmate": "^10.1.0", | |
| 1442 | + "@types/hast": "^3.0.5" | |
| 1443 | + }, | |
| 1444 | + "engines": { | |
| 1445 | + "node": ">=20" | |
| 1446 | + } | |
| 1447 | + }, | |
| 1448 | + "node_modules/source-map-js": { | |
| 1449 | + "version": "1.2.2", | |
| 1450 | + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", | |
| 1451 | + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", | |
| 1452 | + "license": "BSD-3-Clause", | |
| 1453 | + "engines": { | |
| 1454 | + "node": ">=0.10.0" | |
| 1455 | + } | |
| 1456 | + }, | |
| 1457 | + "node_modules/space-separated-tokens": { | |
| 1458 | + "version": "2.0.2", | |
| 1459 | + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", | |
| 1460 | + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", | |
| 1461 | + "license": "MIT", | |
| 1462 | + "funding": { | |
| 1463 | + "type": "github", | |
| 1464 | + "url": "https://github.com/sponsors/wooorm" | |
| 1465 | + } | |
| 1466 | + }, | |
| 1467 | + "node_modules/stringify-entities": { | |
| 1468 | + "version": "4.0.4", | |
| 1469 | + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", | |
| 1470 | + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", | |
| 1471 | + "license": "MIT", | |
| 1472 | + "dependencies": { | |
| 1473 | + "character-entities-html4": "^2.0.0", | |
| 1474 | + "character-entities-legacy": "^3.0.0" | |
| 1475 | + }, | |
| 1476 | + "funding": { | |
| 1477 | + "type": "github", | |
| 1478 | + "url": "https://github.com/sponsors/wooorm" | |
| 1479 | + } | |
| 1480 | + }, | |
| 1481 | + "node_modules/styled-jsx": { | |
| 1482 | + "version": "5.1.6", | |
| 1483 | + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", | |
| 1484 | + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", | |
| 1485 | + "license": "MIT", | |
| 1486 | + "dependencies": { | |
| 1487 | + "client-only": "0.0.1" | |
| 1488 | + }, | |
| 1489 | + "engines": { | |
| 1490 | + "node": ">= 12.0.0" | |
| 1491 | + }, | |
| 1492 | + "peerDependencies": { | |
| 1493 | + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" | |
| 1494 | + }, | |
| 1495 | + "peerDependenciesMeta": { | |
| 1496 | + "@babel/core": { | |
| 1497 | + "optional": true | |
| 1498 | + }, | |
| 1499 | + "babel-plugin-macros": { | |
Différences tronquées : fichier trop long.
Ajoutépackage.json+25−0
| @@ -0,0 +1,25 @@ | ||
| 1 | +{ | |
| 2 | + "name": "dimdams-docs", | |
| 3 | + "version": "1.0.0", | |
| 4 | + "private": true, | |
| 5 | + "description": "Documentation de Dimdam's (docs.dimdams.com) : hébergement, API et CLI, Dimdam's Git, Dimdam's Status.", | |
| 6 | + "scripts": { | |
| 7 | + "dev": "next dev -p 3010", | |
| 8 | + "build": "next build", | |
| 9 | + "check": "node scripts/check-content.mjs", | |
| 10 | + "deploy": "bash scripts/deploy.sh" | |
| 11 | + }, | |
| 12 | + "dependencies": { | |
| 13 | + "marked": "18.1.0", | |
| 14 | + "next": "16.4.4", | |
| 15 | + "react": "19.2.3", | |
| 16 | + "react-dom": "19.2.3", | |
| 17 | + "shiki": "4.6.0" | |
| 18 | + }, | |
| 19 | + "devDependencies": { | |
| 20 | + "@types/node": "^20.19.27", | |
| 21 | + "@types/react": "^19", | |
| 22 | + "@types/react-dom": "^19", | |
| 23 | + "typescript": "^5" | |
| 24 | + } | |
| 25 | +} | |
Binairepublic/icon.webp+0−0
Fichier binaire : différences non affichées.
Binairepublic/logo-mark.png+0−0
Fichier binaire : différences non affichées.
Ajoutéscripts/check-content.mjs+102−0
| @@ -0,0 +1,102 @@ | ||
| 1 | +import fs from "node:fs"; | |
| 2 | +import path from "node:path"; | |
| 3 | + | |
| 4 | +const ROOT = path.resolve(import.meta.dirname, ".."); | |
| 5 | +const CONTENT_DIR = path.join(ROOT, "content"); | |
| 6 | +const ALLOWED_VARS = new Set(["PUBLIC_URL", "MAIN_SITE_URL", "GIT_URL", "STATUS_URL"]); | |
| 7 | +const SECTIONS = ["hebergement", "api", "git", "status"]; | |
| 8 | + | |
| 9 | +let errors = 0; | |
| 10 | +let warnings = 0; | |
| 11 | +let totalPages = 0; | |
| 12 | + | |
| 13 | +const pagesBySection = new Map(); | |
| 14 | + | |
| 15 | +for (const section of SECTIONS) { | |
| 16 | + const dir = path.join(CONTENT_DIR, section); | |
| 17 | + if (!fs.existsSync(dir)) { | |
| 18 | + console.error(`❌ Dossier manquant : ${section}`); | |
| 19 | + errors++; | |
| 20 | + continue; | |
| 21 | + } | |
| 22 | + const files = fs.readdirSync(dir).filter((f) => f.endsWith(".md")); | |
| 23 | + const slugs = new Set(files.map((f) => f.replace(/\.md$/, ""))); | |
| 24 | + pagesBySection.set(section, slugs); | |
| 25 | + | |
| 26 | + for (const file of files) { | |
| 27 | + totalPages++; | |
| 28 | + const fullPath = path.join(dir, file); | |
| 29 | + const content = fs.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n"); | |
| 30 | + | |
| 31 | + const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?/); | |
| 32 | + if (!fmMatch) { | |
| 33 | + console.error(`❌ En-tête manquant : ${section}/${file}`); | |
| 34 | + errors++; | |
| 35 | + continue; | |
| 36 | + } | |
| 37 | + | |
| 38 | + const meta = {}; | |
| 39 | + for (const line of fmMatch[1].split("\n")) { | |
| 40 | + const kv = line.match(/^([a-zA-Z]+):\s*(.*)$/); | |
| 41 | + if (kv) meta[kv[1]] = kv[2].trim().replace(/^"(.*)"$/, "$1"); | |
| 42 | + } | |
| 43 | + | |
| 44 | + if (!meta.title) { | |
| 45 | + console.error(`❌ Titre manquant : ${section}/${file}`); | |
| 46 | + errors++; | |
| 47 | + } | |
| 48 | + if (!meta.description) { | |
| 49 | + console.warn(`⚠️ Description manquante : ${section}/${file}`); | |
| 50 | + warnings++; | |
| 51 | + } | |
| 52 | + | |
| 53 | + // Check variable substitutions | |
| 54 | + const varMatches = content.matchAll(/\{\{([A-Z_]+)\}\}/g); | |
| 55 | + for (const match of varMatches) { | |
| 56 | + if (!ALLOWED_VARS.has(match[1])) { | |
| 57 | + console.error(`❌ Variable inconnue {{${match[1]}}} dans ${section}/${file}`); | |
| 58 | + errors++; | |
| 59 | + } | |
| 60 | + } | |
| 61 | + } | |
| 62 | +} | |
| 63 | + | |
| 64 | +// Check internal links | |
| 65 | +for (const section of SECTIONS) { | |
| 66 | + const dir = path.join(CONTENT_DIR, section); | |
| 67 | + if (!fs.existsSync(dir)) continue; | |
| 68 | + const files = fs.readdirSync(dir).filter((f) => f.endsWith(".md")); | |
| 69 | + | |
| 70 | + for (const file of files) { | |
| 71 | + const fullPath = path.join(dir, file); | |
| 72 | + const content = fs.readFileSync(fullPath, "utf8"); | |
| 73 | + | |
| 74 | + // Matches [text](/section/slug/) or (/section/) | |
| 75 | + const linkMatches = content.matchAll(/\]\((\/[a-z0-9_-]+\/[a-z0-9_#-]*)\)/g); | |
| 76 | + for (const match of linkMatches) { | |
| 77 | + const rawHref = match[1].split("#")[0]; // remove anchor | |
| 78 | + const parts = rawHref.split("/").filter(Boolean); | |
| 79 | + if (parts.length >= 1) { | |
| 80 | + const targetSection = parts[0]; | |
| 81 | + const targetSlug = parts[1] || "index"; | |
| 82 | + const sectionSlugs = pagesBySection.get(targetSection); | |
| 83 | + if (!sectionSlugs) { | |
| 84 | + console.error(`❌ Lien vers section inconnue : ${rawHref} dans ${section}/${file}`); | |
| 85 | + errors++; | |
| 86 | + } else if (!sectionSlugs.has(targetSlug)) { | |
| 87 | + console.error(`❌ Lien brisé : ${rawHref} dans ${section}/${file} (page ${targetSlug}.md introuvable)`); | |
| 88 | + errors++; | |
| 89 | + } | |
| 90 | + } | |
| 91 | + } | |
| 92 | + } | |
| 93 | +} | |
| 94 | + | |
| 95 | +console.log(`\nVérification terminée : ${totalPages} pages analysées.`); | |
| 96 | +console.log(`Erreurs : ${errors}, Avertissements : ${warnings}`); | |
| 97 | + | |
| 98 | +if (errors > 0) { | |
| 99 | + process.exit(1); | |
| 100 | +} else { | |
| 101 | + console.log("✅ Contenu valide !"); | |
| 102 | +} | |
Ajoutéscripts/deploy.sh+33−0
| @@ -0,0 +1,33 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +# Déploie le code committé (HEAD) de Dimdam's Docs sur server-003 : | |
| 3 | +# envoie les fichiers, installe, compile le site statique (out/), sans PM2 car servi directement par Nginx. | |
| 4 | +# bash scripts/deploy.sh (depuis Git Bash sur le PC) | |
| 5 | +set -euo pipefail | |
| 6 | + | |
| 7 | +repo="$(cd "$(dirname "$0")/.." && pwd)" | |
| 8 | +cd "$repo" | |
| 9 | + | |
| 10 | +if [ "$(node -p "require('./package.json').name" 2>/dev/null)" != "dimdams-docs" ]; then | |
| 11 | + echo "Ce script doit rester dans le dépôt dimdams-docs (dossier actuel : $repo)." >&2 | |
| 12 | + exit 1 | |
| 13 | +fi | |
| 14 | +if [ -n "$(git status --porcelain)" ]; then | |
| 15 | + echo "Attention : des modifications ne sont pas committées, seul le dernier commit est envoyé." >&2 | |
| 16 | +fi | |
| 17 | + | |
| 18 | +server="${DEPLOY_SERVER:-root@192.168.1.131}" | |
| 19 | +app="/home/benladamm/dimdams-docs" | |
| 20 | + | |
| 21 | +echo "Déploiement de $(git log --oneline -1) vers $server:$app" | |
| 22 | +git archive HEAD | ssh "$server" "set -e | |
| 23 | + mkdir -p $app | |
| 24 | + cd $app | |
| 25 | + tar -xf - | |
| 26 | + if [ ! -f .env ]; then | |
| 27 | + cp .env.example .env | |
| 28 | + fi | |
| 29 | + npm ci --no-audit --no-fund > /tmp/dd-npm.log 2>&1 || { tail -25 /tmp/dd-npm.log; exit 1; } | |
| 30 | + npm run build > /tmp/dd-build.log 2>&1 || { tail -25 /tmp/dd-build.log; exit 1; } | |
| 31 | + chown -R benladamm:benladamm $app | |
| 32 | + echo 'Compilation statique : OK (out/)'" | |
| 33 | +echo "Terminé : https://docs.dimdams.com" | |
Ajoutésrc/app/[section]/[slug]/page.tsx+24−0
| @@ -0,0 +1,24 @@ | ||
| 1 | +import type { Metadata } from "next"; | |
| 2 | +import { notFound } from "next/navigation"; | |
| 3 | +import { DocPage } from "@/components/DocPage"; | |
| 4 | +import { getPage, listPages } from "@/lib/content"; | |
| 5 | +import { SECTIONS } from "@/lib/sections"; | |
| 6 | + | |
| 7 | +type Params = { params: Promise<{ section: string; slug: string }> }; | |
| 8 | + | |
| 9 | +export const dynamicParams = false; | |
| 10 | +export const generateStaticParams = () => | |
| 11 | + SECTIONS.flatMap((s) => listPages(s.id).filter((p) => p.slug !== "index").map((p) => ({ section: s.id, slug: p.slug }))); | |
| 12 | + | |
| 13 | +export async function generateMetadata({ params }: Params): Promise<Metadata> { | |
| 14 | + const { section, slug } = await params; | |
| 15 | + const page = await getPage(section, slug); | |
| 16 | + return page ? { title: page.title, description: page.description || undefined, alternates: { canonical: page.href } } : {}; | |
| 17 | +} | |
| 18 | + | |
| 19 | +export default async function ContentPage({ params }: Params) { | |
| 20 | + const { section, slug } = await params; | |
| 21 | + const page = await getPage(section, slug); | |
| 22 | + if (!page) notFound(); | |
| 23 | + return <DocPage page={page} pages={listPages(section)} />; | |
| 24 | +} | |
Ajoutésrc/app/[section]/page.tsx+23−0
| @@ -0,0 +1,23 @@ | ||
| 1 | +import type { Metadata } from "next"; | |
| 2 | +import { notFound } from "next/navigation"; | |
| 3 | +import { DocPage } from "@/components/DocPage"; | |
| 4 | +import { getPage, listPages } from "@/lib/content"; | |
| 5 | +import { SECTIONS, sectionById } from "@/lib/sections"; | |
| 6 | + | |
| 7 | +type Params = { params: Promise<{ section: string }> }; | |
| 8 | + | |
| 9 | +export const dynamicParams = false; | |
| 10 | +export const generateStaticParams = () => SECTIONS.map((s) => ({ section: s.id })); | |
| 11 | + | |
| 12 | +export async function generateMetadata({ params }: Params): Promise<Metadata> { | |
| 13 | + const section = sectionById((await params).section); | |
| 14 | + return section ? { title: section.title, description: section.description, alternates: { canonical: `/${section.id}/` } } : {}; | |
| 15 | +} | |
| 16 | + | |
| 17 | +/** Home of a documentation: its index.md. */ | |
| 18 | +export default async function SectionPage({ params }: Params) { | |
| 19 | + const { section } = await params; | |
| 20 | + const page = await getPage(section, "index"); | |
| 21 | + if (!page) notFound(); | |
| 22 | + return <DocPage page={page} pages={listPages(section)} />; | |
| 23 | +} | |
Ajoutésrc/app/globals.css+191−0
| @@ -0,0 +1,191 @@ | ||
| 1 | +/* Dimdam's Docs: Dimdam's colors, dark and light theme (data-theme on <html>, otherwise the system's). */ | |
| 2 | + | |
| 3 | +:root { | |
| 4 | + --canvas: #09090b; --surface: #141417; --surface-2: #0f0f12; --line: rgba(255, 255, 255, 0.08); --line-2: rgba(255, 255, 255, 0.14); | |
| 5 | + --text: #d9dce1; --muted: #9ca3af; --faint: #6b7280; --strong: #fff; | |
| 6 | + --accent: #6b2030; --accent-hover: #561a26; --accent-light: #d0566e; --accent-soft: #f3b4c1; | |
| 7 | + --note: #38bdf8; --note-soft: rgba(56, 189, 248, 0.1); --tip: #22c55e; --tip-soft: rgba(34, 197, 94, 0.1); | |
| 8 | + --warn: #f59e0b; --warn-soft: rgba(245, 158, 11, 0.1); --danger: #f43f5e; --danger-soft: rgba(244, 63, 94, 0.1); | |
| 9 | + --code-bg: #0d0d10; --inline-code: rgba(255, 255, 255, 0.07); | |
| 10 | + color-scheme: dark; | |
| 11 | +} | |
| 12 | +:root[data-theme="light"] { | |
| 13 | + --canvas: #f7f7f8; --surface: #fff; --surface-2: #f1f1f3; --line: rgba(0, 0, 0, 0.08); --line-2: rgba(0, 0, 0, 0.14); | |
| 14 | + --text: #2b2f36; --muted: #4b5563; --faint: #8b93a1; --strong: #111827; | |
| 15 | + --accent-light: #a3283f; --accent-soft: #6b2030; | |
| 16 | + --note: #0284c7; --note-soft: rgba(2, 132, 199, 0.08); --tip: #15803d; --tip-soft: rgba(21, 128, 61, 0.08); | |
| 17 | + --warn: #b45309; --warn-soft: rgba(217, 119, 6, 0.1); --danger: #be123c; --danger-soft: rgba(225, 29, 72, 0.08); | |
| 18 | + --code-bg: #f6f6f8; --inline-code: rgba(0, 0, 0, 0.06); | |
| 19 | + color-scheme: light; | |
| 20 | +} | |
| 21 | +@media (prefers-color-scheme: light) { | |
| 22 | + :root:not([data-theme]) { | |
| 23 | + --canvas: #f7f7f8; --surface: #fff; --surface-2: #f1f1f3; --line: rgba(0, 0, 0, 0.08); --line-2: rgba(0, 0, 0, 0.14); | |
| 24 | + --text: #2b2f36; --muted: #4b5563; --faint: #8b93a1; --strong: #111827; | |
| 25 | + --accent-light: #a3283f; --accent-soft: #6b2030; | |
| 26 | + --note: #0284c7; --note-soft: rgba(2, 132, 199, 0.08); --tip: #15803d; --tip-soft: rgba(21, 128, 61, 0.08); | |
| 27 | + --warn: #b45309; --warn-soft: rgba(217, 119, 6, 0.1); --danger: #be123c; --danger-soft: rgba(225, 29, 72, 0.08); | |
| 28 | + --code-bg: #f6f6f8; --inline-code: rgba(0, 0, 0, 0.06); | |
| 29 | + color-scheme: light; | |
| 30 | + } | |
| 31 | +} | |
| 32 | + | |
| 33 | +* { box-sizing: border-box; } | |
| 34 | +html { scroll-padding-top: 88px; } | |
| 35 | +body { margin: 0; min-height: 100vh; display: flex; flex-direction: column; background: var(--canvas); color: var(--text); font-family: var(--font-inter), system-ui, sans-serif; font-size: 16px; line-height: 1.65; -webkit-font-smoothing: antialiased; } | |
| 36 | +a { color: var(--accent-light); } | |
| 37 | +h1, h2, h3, h4 { font-family: var(--font-outfit), system-ui, sans-serif; color: var(--strong); line-height: 1.25; } | |
| 38 | +code, kbd, pre { font-family: var(--font-mono), ui-monospace, monospace; } | |
| 39 | +:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; border-radius: 4px; } | |
| 40 | + | |
| 41 | +/* Buttons */ | |
| 42 | +.btn { font: inherit; font-size: 13px; font-weight: 600; padding: 8px 14px; border-radius: 10px; border: 1px solid var(--line-2); background: var(--surface); color: var(--text); cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: 6px; } | |
| 43 | +.btn:hover { border-color: var(--faint); } | |
| 44 | +.btn.primary { background: var(--accent); border-color: #c2415a; color: #fff; } | |
| 45 | +.btn.primary:hover { background: var(--accent-hover); } | |
| 46 | +.btn.icon { padding: 8px; } | |
| 47 | + | |
| 48 | +/* Header */ | |
| 49 | +.header { position: sticky; top: 0; z-index: 40; border-bottom: 1px solid var(--line); background: color-mix(in srgb, var(--canvas) 85%, transparent); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); } | |
| 50 | +.header-in { max-width: 1360px; margin: 0 auto; padding: 0 20px; height: 64px; display: flex; align-items: center; gap: 20px; } | |
| 51 | +.brand { display: flex; align-items: center; gap: 10px; font-family: var(--font-outfit), sans-serif; font-weight: 800; font-size: 18px; color: var(--strong); text-decoration: none; flex: none; } | |
| 52 | +.brand img { width: 36px; height: 36px; object-fit: contain; } | |
| 53 | +.tag { font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; padding: 3px 8px; border-radius: 999px; border: 1px solid var(--line-2); color: var(--accent-soft); } | |
| 54 | +.tabs { display: flex; gap: 2px; font-size: 14px; } | |
| 55 | +.tabs a { padding: 7px 12px; border-radius: 10px; color: var(--muted); text-decoration: none; white-space: nowrap; } | |
| 56 | +.tabs a:hover, .tabs a[aria-current="page"] { color: var(--strong); background: var(--surface); } | |
| 57 | +.tabs a[aria-current="page"] { box-shadow: inset 0 -2px 0 var(--accent-light); } | |
| 58 | +.tabs-mobile { display: none; } | |
| 59 | +.header-right { margin-left: auto; display: flex; align-items: center; gap: 8px; } | |
| 60 | +.search-button { font: inherit; font-size: 13px; display: inline-flex; align-items: center; gap: 8px; padding: 7px 10px; min-width: 210px; border-radius: 10px; border: 1px solid var(--line-2); background: var(--surface-2); color: var(--faint); cursor: pointer; } | |
| 61 | +.search-button:hover { border-color: var(--faint); color: var(--muted); } | |
| 62 | +.search-button kbd { margin-left: auto; } | |
| 63 | +kbd { font-size: 11px; padding: 1px 6px; border-radius: 6px; border: 1px solid var(--line-2); background: var(--surface); color: var(--muted); } | |
| 64 | + | |
| 65 | +/* Search */ | |
| 66 | +.search-overlay { position: fixed; inset: 0; z-index: 60; background: rgba(0, 0, 0, 0.55); backdrop-filter: blur(3px); display: flex; justify-content: center; padding: 10vh 16px 16px; } | |
| 67 | +.search-box { width: 100%; max-width: 640px; max-height: 75vh; display: flex; flex-direction: column; border-radius: 16px; border: 1px solid var(--line-2); background: var(--surface); box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45); overflow: hidden; } | |
| 68 | +.search-box input { font: inherit; font-size: 16px; padding: 16px 18px; border: 0; border-bottom: 1px solid var(--line); background: transparent; color: var(--strong); outline: none; } | |
| 69 | +.search-results { list-style: none; margin: 0; padding: 6px; overflow-y: auto; } | |
| 70 | +.search-results button { width: 100%; text-align: left; font: inherit; display: flex; flex-direction: column; gap: 2px; padding: 10px 12px; border-radius: 10px; border: 0; background: transparent; color: var(--text); cursor: pointer; } | |
| 71 | +.search-results button.active { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--accent-light); } | |
| 72 | +.search-where { font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--faint); } | |
| 73 | +.search-title { font-weight: 600; color: var(--strong); } | |
| 74 | +.search-heading { font-weight: 500; color: var(--muted); } | |
| 75 | +.search-snippet { font-size: 13px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| 76 | +.search-empty { padding: 14px 12px; color: var(--muted); font-size: 14px; } | |
| 77 | +.search-help { margin: 0; padding: 10px 16px; border-top: 1px solid var(--line); font-size: 12px; color: var(--faint); } | |
| 78 | + | |
| 79 | +/* Home */ | |
| 80 | +.home { max-width: 1160px; margin: 0 auto; padding: 56px 20px 72px; width: 100%; flex: 1; } | |
| 81 | +.hero { text-align: center; margin-bottom: 44px; } | |
| 82 | +.hero h1 { font-size: clamp(30px, 5vw, 44px); margin: 0 0 12px; letter-spacing: -0.02em; } | |
| 83 | +.hero p { max-width: 640px; margin: 0 auto; color: var(--muted); font-size: 17px; } | |
| 84 | +.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; } | |
| 85 | +.card { display: flex; flex-direction: column; padding: 22px; border-radius: 16px; border: 1px solid var(--line); background: var(--surface); text-decoration: none; color: var(--text); transition: border-color 0.15s, transform 0.15s; } | |
| 86 | +.card:hover { border-color: color-mix(in srgb, var(--accent-light) 45%, transparent); transform: translateY(-2px); } | |
| 87 | +.card h2 { margin: 0 0 6px; font-size: 19px; } | |
| 88 | +.card p { margin: 0 0 14px; color: var(--muted); font-size: 14px; } | |
| 89 | +.card ul { margin: 0 0 14px; padding-left: 18px; font-size: 14px; color: var(--muted); } | |
| 90 | +.card-more { margin-top: auto; font-size: 13px; font-weight: 600; color: var(--accent-light); } | |
| 91 | + | |
| 92 | +/* Documentation page */ | |
| 93 | +.doc { max-width: 1360px; margin: 0 auto; width: 100%; flex: 1; display: grid; grid-template-columns: 250px minmax(0, 1fr) 220px; gap: 40px; padding: 0 20px; } | |
| 94 | +.sidebar, .toc { position: sticky; top: 64px; align-self: start; max-height: calc(100vh - 64px); overflow-y: auto; padding: 28px 0; } | |
| 95 | +.sidebar-title, .toc-title { margin: 0 0 10px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--faint); } | |
| 96 | +.sidebar ul, .toc ul, .sidebar-mobile ul { list-style: none; margin: 0; padding: 0; } | |
| 97 | +.sidebar a, .sidebar-mobile a { display: block; padding: 6px 10px; border-radius: 8px; font-size: 14px; color: var(--muted); text-decoration: none; } | |
| 98 | +.sidebar a:hover, .sidebar-mobile a:hover { color: var(--strong); background: var(--surface); } | |
| 99 | +.sidebar a[aria-current="page"], .sidebar-mobile a[aria-current="page"] { color: var(--strong); background: var(--surface); font-weight: 600; box-shadow: inset 2px 0 0 var(--accent-light); } | |
| 100 | +.toc a { display: block; padding: 4px 0; font-size: 13px; color: var(--muted); text-decoration: none; } | |
| 101 | +.toc a:hover { color: var(--strong); } | |
| 102 | +.toc li.sub a { padding-left: 12px; font-size: 12.5px; } | |
| 103 | +.sidebar-mobile { display: none; } | |
| 104 | +.article { min-width: 0; max-width: 780px; padding: 32px 0 64px; } | |
| 105 | +.breadcrumb { margin: 0 0 8px; font-size: 13px; color: var(--faint); } | |
| 106 | +.breadcrumb a { color: var(--muted); text-decoration: none; } | |
| 107 | +.breadcrumb a:hover { color: var(--accent-light); } | |
| 108 | +.article > h1 { font-size: clamp(28px, 4vw, 36px); margin: 0 0 10px; letter-spacing: -0.015em; } | |
| 109 | +.lead { margin: 0 0 28px; font-size: 18px; color: var(--muted); } | |
| 110 | + | |
| 111 | +/* Content */ | |
| 112 | +.prose h2 { font-size: 23px; margin: 44px 0 12px; padding-top: 16px; border-top: 1px solid var(--line); } | |
| 113 | +.prose h3 { font-size: 18px; margin: 30px 0 8px; } | |
| 114 | +.prose h4 { font-size: 16px; margin: 22px 0 6px; } | |
| 115 | +.prose h2, .prose h3, .prose h4 { position: relative; } | |
| 116 | +.prose .anchor { position: absolute; left: -22px; color: var(--faint); text-decoration: none; opacity: 0; font-weight: 500; } | |
| 117 | +.prose h2:hover .anchor, .prose h3:hover .anchor, .prose h4:hover .anchor { opacity: 1; } | |
| 118 | +.prose p, .prose ul, .prose ol { margin: 0 0 16px; } | |
| 119 | +.prose li { margin: 4px 0; } | |
| 120 | +.prose li > ul, .prose li > ol { margin: 4px 0; } | |
| 121 | +.prose strong { color: var(--strong); } | |
| 122 | +.prose a { text-underline-offset: 3px; } | |
| 123 | +.prose :not(pre) > code { font-size: 0.88em; padding: 2px 6px; border-radius: 6px; background: var(--inline-code); color: var(--strong); white-space: break-spaces; } | |
| 124 | +.prose hr { border: 0; border-top: 1px solid var(--line); margin: 32px 0; } | |
| 125 | +.prose blockquote { margin: 0 0 16px; padding: 2px 16px; border-left: 3px solid var(--line-2); color: var(--muted); } | |
| 126 | +.prose img { max-width: 100%; border-radius: 12px; border: 1px solid var(--line); } | |
| 127 | + | |
| 128 | +.code { position: relative; margin: 0 0 18px; border-radius: 12px; border: 1px solid var(--line); background: var(--code-bg); overflow: hidden; } | |
| 129 | +.code pre { margin: 0; padding: 16px 18px; overflow-x: auto; font-size: 13.5px; line-height: 1.6; background: transparent !important; } | |
| 130 | +.code-lang { position: absolute; top: 8px; left: 14px; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--faint); font-family: var(--font-mono), monospace; pointer-events: none; } | |
| 131 | +.code:has(.code-lang) pre { padding-top: 30px; } | |
| 132 | +.code-copy { position: absolute; top: 7px; right: 8px; font: inherit; font-size: 12px; padding: 3px 9px; border-radius: 7px; border: 1px solid var(--line-2); background: var(--surface); color: var(--muted); cursor: pointer; opacity: 0; transition: opacity 0.15s; } | |
| 133 | +.code:hover .code-copy, .code-copy:focus-visible { opacity: 1; } | |
| 134 | +.shiki span { color: var(--shiki-dark); } | |
| 135 | +:root[data-theme="light"] .shiki span { color: var(--shiki-light); } | |
| 136 | +@media (prefers-color-scheme: light) { :root:not([data-theme]) .shiki span { color: var(--shiki-light); } } | |
| 137 | + | |
| 138 | +.table { margin: 0 0 18px; overflow-x: auto; border: 1px solid var(--line); border-radius: 12px; } | |
| 139 | +.table table { width: 100%; border-collapse: collapse; font-size: 14px; } | |
| 140 | +.table th, .table td { padding: 9px 14px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--line); } | |
| 141 | +.table th { font-weight: 600; color: var(--strong); background: var(--surface-2); } | |
| 142 | +.table tr:last-child td { border-bottom: 0; } | |
| 143 | + | |
| 144 | +.callout { margin: 0 0 18px; padding: 12px 16px; border-radius: 12px; border: 1px solid var(--line); border-left: 3px solid var(--note); background: var(--note-soft); } | |
| 145 | +.callout > :last-child { margin-bottom: 0; } | |
| 146 | +.callout-title { margin: 0 0 4px !important; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--note); } | |
| 147 | +.callout.tip { border-left-color: var(--tip); background: var(--tip-soft); } .callout.tip .callout-title { color: var(--tip); } | |
| 148 | +.callout.warning { border-left-color: var(--warn); background: var(--warn-soft); } .callout.warning .callout-title { color: var(--warn); } | |
| 149 | +.callout.danger { border-left-color: var(--danger); background: var(--danger-soft); } .callout.danger .callout-title { color: var(--danger); } | |
| 150 | + | |
| 151 | +.pager { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--line); } | |
| 152 | +.pager-link { display: flex; flex-direction: column; gap: 2px; padding: 14px 16px; border-radius: 12px; border: 1px solid var(--line); background: var(--surface); color: var(--strong); text-decoration: none; font-weight: 600; font-size: 15px; } | |
| 153 | +.pager-link span { font-size: 12px; font-weight: 500; color: var(--faint); } | |
| 154 | +.pager-link:hover { border-color: color-mix(in srgb, var(--accent-light) 45%, transparent); } | |
| 155 | +.pager-link.next { text-align: right; grid-column: 2; } | |
| 156 | + | |
| 157 | +/* Error page and footer */ | |
| 158 | +.error-page { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 64px 20px; } | |
| 159 | +.error-page img { object-fit: contain; margin-bottom: 18px; } | |
| 160 | +.error-code { margin: 0; font-family: var(--font-mono), monospace; font-weight: 600; letter-spacing: 0.2em; color: var(--accent-light); } | |
| 161 | +.error-page h1 { font-size: 34px; margin: 6px 0 10px; } | |
| 162 | +.error-page > p:not(.error-code) { max-width: 480px; color: var(--muted); margin: 0 0 24px; } | |
| 163 | +.footer { border-top: 1px solid var(--line); color: var(--faint); font-size: 13px; } | |
| 164 | +.footer-in { max-width: 1360px; margin: 0 auto; padding: 20px; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; } | |
| 165 | +.footer-links { display: flex; gap: 16px; } | |
| 166 | +.footer-links a { color: var(--muted); text-decoration: none; } | |
| 167 | +.footer-links a:hover { color: var(--accent-light); } | |
| 168 | + | |
| 169 | +@media (max-width: 1180px) { | |
| 170 | + .doc { grid-template-columns: 230px minmax(0, 1fr); } | |
| 171 | + .toc { display: none; } | |
| 172 | +} | |
| 173 | +@media (max-width: 900px) { | |
| 174 | + .header-in .tabs { display: none; } | |
| 175 | + .tabs-mobile { display: flex; overflow-x: auto; padding: 0 12px 10px; max-width: 1360px; margin: 0 auto; } | |
| 176 | + .search-button { min-width: 0; } | |
| 177 | + .search-label, .search-button kbd, .header-right .site { display: none; } | |
| 178 | + .doc { grid-template-columns: minmax(0, 1fr); } | |
| 179 | + .sidebar { display: none; } | |
| 180 | + .sidebar-mobile { display: block; margin: 0 0 18px; padding: 10px 12px; border-radius: 12px; border: 1px solid var(--line); background: var(--surface); } | |
| 181 | + .sidebar-mobile summary { cursor: pointer; font-size: 14px; font-weight: 600; color: var(--strong); } | |
| 182 | + .sidebar-mobile ul { margin-top: 8px; } | |
| 183 | + .article { padding-top: 22px; } | |
| 184 | + .prose .anchor { display: none; } | |
| 185 | + .header .tabs-mobile a[aria-current="page"] { box-shadow: none; } | |
| 186 | +} | |
| 187 | +@media (max-width: 560px) { | |
| 188 | + .tag { display: none; } | |
| 189 | + .pager { grid-template-columns: 1fr; } | |
| 190 | + .pager-link.next { grid-column: 1; } | |
| 191 | +} | |
Ajoutésrc/app/layout.tsx+48−0
| @@ -0,0 +1,48 @@ | ||
| 1 | +import type { Metadata } from "next"; | |
| 2 | +import { Inter, JetBrains_Mono, Outfit } from "next/font/google"; | |
| 3 | +import { Header } from "@/components/Header"; | |
| 4 | +import { CodeCopy } from "@/components/CodeCopy"; | |
| 5 | +import { requireUrl } from "@/lib/env"; | |
| 6 | +import { SECTIONS } from "@/lib/sections"; | |
| 7 | +import "./globals.css"; | |
| 8 | + | |
| 9 | +// Fonts downloaded at build time and served by the site: no request to Google from the pages. | |
| 10 | +const inter = Inter({ subsets: ["latin"], variable: "--font-inter", display: "swap" }); | |
| 11 | +const outfit = Outfit({ subsets: ["latin"], weight: ["500", "600", "700", "800"], variable: "--font-outfit", display: "swap" }); | |
| 12 | +const mono = JetBrains_Mono({ subsets: ["latin"], weight: ["400", "500"], variable: "--font-mono", display: "swap" }); | |
| 13 | + | |
| 14 | +export const metadata: Metadata = { | |
| 15 | + metadataBase: new URL(requireUrl("PUBLIC_URL")), | |
| 16 | + title: { default: "Documentation — Dimdam's", template: "%s — Documentation Dimdam's" }, | |
| 17 | + description: "Documentation de Dimdam's : hébergement, API et CLI, Dimdam's Git et Dimdam's Status.", | |
| 18 | + icons: { icon: "/icon.webp" }, | |
| 19 | +}; | |
| 20 | + | |
| 21 | +// Theme chosen by the visitor (localStorage), set before the first paint to avoid a flash. | |
| 22 | +const THEME_SCRIPT = `try{var t=localStorage.getItem("ds_theme");if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch(e){}`; | |
| 23 | + | |
| 24 | +export default function RootLayout({ children }: { children: React.ReactNode }) { | |
| 25 | + const mainSite = requireUrl("MAIN_SITE_URL"); | |
| 26 | + return ( | |
| 27 | + <html lang="fr" className={`${inter.variable} ${outfit.variable} ${mono.variable}`} suppressHydrationWarning> | |
| 28 | + <head> | |
| 29 | + <script dangerouslySetInnerHTML={{ __html: THEME_SCRIPT }} /> | |
| 30 | + </head> | |
| 31 | + <body> | |
| 32 | + <Header sections={SECTIONS.map(({ id, title }) => ({ id, title }))} mainSite={mainSite} /> | |
| 33 | + {children} | |
| 34 | + <footer className="footer"> | |
| 35 | + <div className="footer-in"> | |
| 36 | + <span>© 2021 – {new Date().getFullYear()} Dimdam's · Hébergé en France</span> | |
| 37 | + <span className="footer-links"> | |
| 38 | + <a href={mainSite}>dimdams.com</a> | |
| 39 | + <a href={requireUrl("STATUS_URL")}>État des services</a> | |
| 40 | + <a href={requireUrl("GIT_URL")}>Dimdam's Git</a> | |
| 41 | + </span> | |
| 42 | + </div> | |
| 43 | + </footer> | |
| 44 | + <CodeCopy /> | |
| 45 | + </body> | |
| 46 | + </html> | |
| 47 | + ); | |
| 48 | +} | |
Ajoutésrc/app/not-found.tsx+17−0
| @@ -0,0 +1,17 @@ | ||
| 1 | +import Image from "next/image"; | |
| 2 | +import Link from "next/link"; | |
| 3 | + | |
| 4 | +export const metadata = { title: "Page introuvable", robots: { index: false } }; | |
| 5 | + | |
| 6 | +/** 404 (out/404.html, served by nginx for an unknown address). */ | |
| 7 | +export default function NotFound() { | |
| 8 | + return ( | |
| 9 | + <main className="error-page"> | |
| 10 | + <Image src="/logo-mark.png" alt="" width={64} height={64} /> | |
| 11 | + <p className="error-code">404</p> | |
| 12 | + <h1>Page introuvable</h1> | |
| 13 | + <p>Cette page n'existe pas, ou elle a été déplacée lors du passage à la nouvelle documentation. La recherche (Ctrl K) vous aidera à la retrouver.</p> | |
| 14 | + <Link href="/" className="btn primary">Accueil de la documentation</Link> | |
| 15 | + </main> | |
| 16 | + ); | |
| 17 | +} | |
Ajoutésrc/app/page.tsx+32−0
| @@ -0,0 +1,32 @@ | ||
| 1 | +import Link from "next/link"; | |
| 2 | +import { listPages } from "@/lib/content"; | |
| 3 | +import { SECTIONS } from "@/lib/sections"; | |
| 4 | + | |
| 5 | +/** Home: one card per documentation, with its first pages. */ | |
| 6 | +export default function Home() { | |
| 7 | + return ( | |
| 8 | + <main className="home"> | |
| 9 | + <section className="hero"> | |
| 10 | + <h1>Documentation Dimdam's</h1> | |
| 11 | + <p>Tout pour héberger, automatiser et suivre vos projets sur Dimdam's. Choisissez une documentation, ou cherchez directement (Ctrl K).</p> | |
| 12 | + </section> | |
| 13 | + <div className="cards"> | |
| 14 | + {SECTIONS.map((s) => { | |
| 15 | + const pages = listPages(s.id).filter((p) => p.slug !== "index"); | |
| 16 | + return ( | |
| 17 | + <Link key={s.id} href={`/${s.id}/`} className="card"> | |
| 18 | + <h2>{s.title}</h2> | |
| 19 | + <p>{s.description}</p> | |
| 20 | + <ul> | |
| 21 | + {pages.slice(0, 4).map((p) => ( | |
| 22 | + <li key={p.slug}>{p.title}</li> | |
| 23 | + ))} | |
| 24 | + </ul> | |
| 25 | + <span className="card-more">{pages.length} pages →</span> | |
| 26 | + </Link> | |
| 27 | + ); | |
| 28 | + })} | |
| 29 | + </div> | |
| 30 | + </main> | |
| 31 | + ); | |
| 32 | +} | |
Ajoutésrc/app/robots.ts+8−0
| @@ -0,0 +1,8 @@ | ||
| 1 | +import type { MetadataRoute } from "next"; | |
| 2 | +import { requireUrl } from "@/lib/env"; | |
| 3 | + | |
| 4 | +export const dynamic = "force-static"; | |
| 5 | + | |
| 6 | +export default function robots(): MetadataRoute.Robots { | |
| 7 | + return { rules: { userAgent: "*", allow: "/" }, sitemap: `${requireUrl("PUBLIC_URL")}/sitemap.xml` }; | |
| 8 | +} | |
Ajoutésrc/app/search-index.json/route.ts+21−0
| @@ -0,0 +1,21 @@ | ||
| 1 | +import { allPages, getPage } from "@/lib/content"; | |
| 2 | +import { sectionById } from "@/lib/sections"; | |
| 3 | + | |
| 4 | +// /search-index.json, written at build time: what the search (src/components/Search.tsx) looks into. | |
| 5 | +export const dynamic = "force-static"; | |
| 6 | + | |
| 7 | +export async function GET() { | |
| 8 | + const entries = []; | |
| 9 | + for (const meta of allPages()) { | |
| 10 | + const page = await getPage(meta.section, meta.slug); | |
| 11 | + if (!page) continue; | |
| 12 | + entries.push({ | |
| 13 | + href: page.href, | |
| 14 | + section: sectionById(page.section)!.title, | |
| 15 | + title: page.slug === "index" ? `${sectionById(page.section)!.title} : présentation` : page.title, | |
| 16 | + headings: page.toc.map(({ id, text }) => ({ id, text })), | |
| 17 | + text: `${page.description} ${page.text}`.slice(0, 6000), | |
| 18 | + }); | |
| 19 | + } | |
| 20 | + return Response.json(entries); | |
| 21 | +} | |
Ajoutésrc/app/sitemap.ts+10−0
| @@ -0,0 +1,10 @@ | ||
| 1 | +import type { MetadataRoute } from "next"; | |
| 2 | +import { allPages } from "@/lib/content"; | |
| 3 | +import { requireUrl } from "@/lib/env"; | |
| 4 | + | |
| 5 | +export const dynamic = "force-static"; | |
| 6 | + | |
| 7 | +export default function sitemap(): MetadataRoute.Sitemap { | |
| 8 | + const base = requireUrl("PUBLIC_URL"); | |
| 9 | + return [{ url: `${base}/` }, ...allPages().map((p) => ({ url: `${base}${p.href}` }))]; | |
| 10 | +} | |
Ajoutésrc/components/CodeCopy.tsx+24−0
| @@ -0,0 +1,24 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect } from "react"; | |
| 4 | + | |
| 5 | +/** "Copier" buttons of the code blocks (rendered in the HTML by src/lib/markdown.ts). */ | |
| 6 | +export function CodeCopy() { | |
| 7 | + useEffect(() => { | |
| 8 | + const onClick = async (event: MouseEvent) => { | |
| 9 | + const button = (event.target as Element | null)?.closest?.(".code-copy") as HTMLButtonElement | null; | |
| 10 | + if (!button) return; | |
| 11 | + const code = button.parentElement?.querySelector("pre")?.innerText ?? ""; | |
| 12 | + try { | |
| 13 | + await navigator.clipboard.writeText(code.replace(/\n$/, "")); | |
| 14 | + button.textContent = "Copié"; | |
| 15 | + } catch { | |
| 16 | + button.textContent = "Copie impossible"; | |
| 17 | + } | |
| 18 | + setTimeout(() => (button.textContent = "Copier"), 1500); | |
| 19 | + }; | |
| 20 | + document.addEventListener("click", onClick); | |
| 21 | + return () => document.removeEventListener("click", onClick); | |
| 22 | + }, []); | |
| 23 | + return null; | |
| 24 | +} | |
Ajoutésrc/components/DocPage.tsx+76−0
| @@ -0,0 +1,76 @@ | ||
| 1 | +import Link from "next/link"; | |
| 2 | +import type { Page, PageMeta } from "@/lib/content"; | |
| 3 | +import { sectionById } from "@/lib/sections"; | |
| 4 | + | |
| 5 | +/** A page of a documentation: pages of the section on the left, the text, its outline on the right. */ | |
| 6 | +export function DocPage({ page, pages }: { page: Page; pages: PageMeta[] }) { | |
| 7 | + const section = sectionById(page.section)!; | |
| 8 | + const at = pages.findIndex((p) => p.slug === page.slug); | |
| 9 | + const previous = at > 0 ? pages[at - 1] : null; | |
| 10 | + const next = at >= 0 && at < pages.length - 1 ? pages[at + 1] : null; | |
| 11 | + | |
| 12 | + const nav = ( | |
| 13 | + <ul> | |
| 14 | + {pages.map((p) => ( | |
| 15 | + <li key={p.slug}> | |
| 16 | + <Link href={p.href} aria-current={p.slug === page.slug ? "page" : undefined}> | |
| 17 | + {p.slug === "index" ? "Présentation" : p.title} | |
| 18 | + </Link> | |
| 19 | + </li> | |
| 20 | + ))} | |
| 21 | + </ul> | |
| 22 | + ); | |
| 23 | + | |
| 24 | + return ( | |
| 25 | + <div className="doc"> | |
| 26 | + <aside className="sidebar" aria-label={`Pages : ${section.title}`}> | |
| 27 | + <p className="sidebar-title">{section.title}</p> | |
| 28 | + {nav} | |
| 29 | + </aside> | |
| 30 | + | |
| 31 | + <main className="article"> | |
| 32 | + <details className="sidebar-mobile"> | |
| 33 | + <summary>{section.title} : toutes les pages</summary> | |
| 34 | + {nav} | |
| 35 | + </details> | |
| 36 | + <p className="breadcrumb"> | |
| 37 | + <Link href={`/${section.id}/`}>{section.title}</Link> | |
| 38 | + {page.slug !== "index" && <> › {page.title}</>} | |
| 39 | + </p> | |
| 40 | + <h1>{page.title}</h1> | |
| 41 | + {page.description && <p className="lead">{page.description}</p>} | |
| 42 | + <div className="prose" dangerouslySetInnerHTML={{ __html: page.html }} /> | |
| 43 | + | |
| 44 | + <nav className="pager" aria-label="Page précédente et suivante"> | |
| 45 | + {previous ? ( | |
| 46 | + <Link href={previous.href} className="pager-link"> | |
| 47 | + <span>Précédent</span> | |
| 48 | + {previous.slug === "index" ? "Présentation" : previous.title} | |
| 49 | + </Link> | |
| 50 | + ) : ( | |
| 51 | + <span /> | |
| 52 | + )} | |
| 53 | + {next && ( | |
| 54 | + <Link href={next.href} className="pager-link next"> | |
| 55 | + <span>Suivant</span> | |
| 56 | + {next.title} | |
| 57 | + </Link> | |
| 58 | + )} | |
| 59 | + </nav> | |
| 60 | + </main> | |
| 61 | + | |
| 62 | + {page.toc.length > 1 && ( | |
| 63 | + <aside className="toc" aria-label="Sur cette page"> | |
| 64 | + <p className="toc-title">Sur cette page</p> | |
| 65 | + <ul> | |
| 66 | + {page.toc.map((t) => ( | |
| 67 | + <li key={t.id} className={t.depth === 3 ? "sub" : ""}> | |
| 68 | + <a href={`#${t.id}`}>{t.text}</a> | |
| 69 | + </li> | |
| 70 | + ))} | |
| 71 | + </ul> | |
| 72 | + </aside> | |
| 73 | + )} | |
| 74 | + </div> | |
| 75 | + ); | |
| 76 | +} | |
Ajoutésrc/components/Header.tsx+39−0
| @@ -0,0 +1,39 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import Image from "next/image"; | |
| 4 | +import Link from "next/link"; | |
| 5 | +import { usePathname } from "next/navigation"; | |
| 6 | +import { Search } from "./Search"; | |
| 7 | +import { ThemeToggle } from "./ThemeToggle"; | |
| 8 | + | |
| 9 | +/** Header: logo, one tab per documentation, search, theme and the main site. */ | |
| 10 | +export function Header({ sections, mainSite }: { sections: { id: string; title: string }[]; mainSite: string }) { | |
| 11 | + const pathname = usePathname(); | |
| 12 | + const current = pathname.split("/")[1] ?? ""; | |
| 13 | + const tabs = sections.map((s) => ( | |
| 14 | + <Link key={s.id} href={`/${s.id}/`} aria-current={s.id === current ? "page" : undefined}> | |
| 15 | + {s.title} | |
| 16 | + </Link> | |
| 17 | + )); | |
| 18 | + | |
| 19 | + return ( | |
| 20 | + <header className="header"> | |
| 21 | + <div className="header-in"> | |
| 22 | + <Link href="/" className="brand"> | |
| 23 | + <Image src="/logo-mark.png" alt="" width={36} height={36} priority /> | |
| 24 | + <span>Dimdam's</span> | |
| 25 | + <span className="tag">Docs</span> | |
| 26 | + </Link> | |
| 27 | + <nav className="tabs" aria-label="Documentations">{tabs}</nav> | |
| 28 | + <div className="header-right"> | |
| 29 | + <Search /> | |
| 30 | + <ThemeToggle /> | |
| 31 | + <a className="btn primary site" href={mainSite}> | |
| 32 | + dimdams.com | |
| 33 | + </a> | |
| 34 | + </div> | |
| 35 | + </div> | |
| 36 | + <nav className="tabs tabs-mobile" aria-label="Documentations">{tabs}</nav> | |
| 37 | + </header> | |
| 38 | + ); | |
| 39 | +} | |
Ajoutésrc/components/Search.tsx+155−0
| @@ -0,0 +1,155 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | |
| 4 | +import { useRouter } from "next/navigation"; | |
| 5 | + | |
| 6 | +interface Entry { | |
| 7 | + href: string; | |
| 8 | + section: string; | |
| 9 | + title: string; | |
| 10 | + headings: { id: string; text: string }[]; | |
| 11 | + text: string; | |
| 12 | +} | |
| 13 | + | |
| 14 | +const norm = (s: string) => | |
| 15 | + s | |
| 16 | + .normalize("NFD") | |
| 17 | + .replace(/[̀-ͯ]/g, "") | |
| 18 | + .toLowerCase(); | |
| 19 | + | |
| 20 | +/** Best matches of the query: every word must appear; title, then headings, then text. */ | |
| 21 | +function search(index: Entry[], query: string) { | |
| 22 | + const words = norm(query).split(/\s+/).filter((w) => w.length > 1); | |
| 23 | + if (words.length === 0) return []; | |
| 24 | + const results: { entry: Entry; score: number; anchor: string | null; snippet: string }[] = []; | |
| 25 | + for (const entry of index) { | |
| 26 | + const title = norm(entry.title); | |
| 27 | + const text = norm(entry.text); | |
| 28 | + const headings = entry.headings.map((h) => ({ ...h, n: norm(h.text) })); | |
| 29 | + if (!words.every((w) => title.includes(w) || text.includes(w) || headings.some((h) => h.n.includes(w)))) continue; | |
| 30 | + let score = 0; | |
| 31 | + for (const w of words) { | |
| 32 | + if (title.includes(w)) score += 10; | |
| 33 | + if (headings.some((h) => h.n.includes(w))) score += 4; | |
| 34 | + if (text.includes(w)) score += 1; | |
| 35 | + } | |
| 36 | + const heading = headings.find((h) => words.every((w) => h.n.includes(w))) ?? headings.find((h) => words.some((w) => h.n.includes(w))); | |
| 37 | + const at = text.indexOf(words[0]); | |
| 38 | + const start = Math.max(0, at - 50); | |
| 39 | + const snippet = at >= 0 ? `${start > 0 ? "…" : ""}${entry.text.slice(start, start + 150)}…` : entry.text.slice(0, 150); | |
| 40 | + results.push({ entry, score, anchor: heading?.id ?? null, snippet }); | |
| 41 | + } | |
| 42 | + return results.sort((a, b) => b.score - a.score).slice(0, 8); | |
| 43 | +} | |
| 44 | + | |
| 45 | +/** Search of the whole documentation (Ctrl+K or "/"), from the index built with the site. */ | |
| 46 | +export function Search() { | |
| 47 | + const router = useRouter(); | |
| 48 | + const [open, setOpen] = useState(false); | |
| 49 | + const [query, setQuery] = useState(""); | |
| 50 | + const [index, setIndex] = useState<Entry[] | null>(null); | |
| 51 | + const [cursor, setCursor] = useState(0); | |
| 52 | + const input = useRef<HTMLInputElement>(null); | |
| 53 | + | |
| 54 | + const show = useCallback(() => { | |
| 55 | + setOpen(true); | |
| 56 | + if (!index) { | |
| 57 | + fetch("/search-index.json") | |
| 58 | + .then((r) => r.json()) | |
| 59 | + .then((data: Entry[]) => setIndex(data)) | |
| 60 | + .catch(() => setIndex([])); | |
| 61 | + } | |
| 62 | + }, [index]); | |
| 63 | + | |
| 64 | + useEffect(() => { | |
| 65 | + const onKey = (e: KeyboardEvent) => { | |
| 66 | + const typing = (e.target as HTMLElement | null)?.closest?.("input, textarea, [contenteditable]"); | |
| 67 | + if ((e.key === "k" && (e.ctrlKey || e.metaKey)) || (e.key === "/" && !typing)) { | |
| 68 | + e.preventDefault(); | |
| 69 | + show(); | |
| 70 | + } | |
| 71 | + }; | |
| 72 | + document.addEventListener("keydown", onKey); | |
| 73 | + return () => document.removeEventListener("keydown", onKey); | |
| 74 | + }, [show]); | |
| 75 | + | |
| 76 | + useEffect(() => { | |
| 77 | + if (!open) return; | |
| 78 | + input.current?.focus(); | |
| 79 | + const overflow = document.body.style.overflow; | |
| 80 | + document.body.style.overflow = "hidden"; | |
| 81 | + return () => { | |
| 82 | + document.body.style.overflow = overflow; | |
| 83 | + }; | |
| 84 | + }, [open]); | |
| 85 | + | |
| 86 | + const results = useMemo(() => (index ? search(index, query) : []), [index, query]); | |
| 87 | + | |
| 88 | + const close = () => { | |
| 89 | + setOpen(false); | |
| 90 | + setQuery(""); | |
| 91 | + setCursor(0); | |
| 92 | + }; | |
| 93 | + const go = (r: (typeof results)[number]) => { | |
| 94 | + close(); | |
| 95 | + router.push(r.anchor ? `${r.entry.href}#${r.anchor}` : r.entry.href); | |
| 96 | + }; | |
| 97 | + | |
| 98 | + const onKeyDown = (e: React.KeyboardEvent) => { | |
| 99 | + if (e.key === "Escape") close(); | |
| 100 | + if (e.key === "ArrowDown" || e.key === "ArrowUp") { | |
| 101 | + e.preventDefault(); | |
| 102 | + if (results.length) setCursor((c) => (c + (e.key === "ArrowDown" ? 1 : results.length - 1)) % results.length); | |
| 103 | + } | |
| 104 | + if (e.key === "Enter" && results[cursor]) go(results[cursor]); | |
| 105 | + }; | |
| 106 | + | |
| 107 | + return ( | |
| 108 | + <> | |
| 109 | + <button type="button" className="search-button" onClick={show} aria-label="Rechercher dans la documentation"> | |
| 110 | + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"> | |
| 111 | + <circle cx="11" cy="11" r="7" /> | |
| 112 | + <path d="m20 20-3.5-3.5" /> | |
| 113 | + </svg> | |
| 114 | + <span className="search-label">Rechercher</span> | |
| 115 | + <kbd>Ctrl K</kbd> | |
| 116 | + </button> | |
| 117 | + {open && ( | |
| 118 | + <div className="search-overlay" role="dialog" aria-modal="true" aria-label="Rechercher" onMouseDown={(e) => e.target === e.currentTarget && close()}> | |
| 119 | + <div className="search-box"> | |
| 120 | + <input | |
| 121 | + ref={input} | |
| 122 | + value={query} | |
| 123 | + onChange={(e) => { | |
| 124 | + setQuery(e.target.value); | |
| 125 | + setCursor(0); | |
| 126 | + }} | |
| 127 | + onKeyDown={onKeyDown} | |
| 128 | + placeholder="Rechercher : clé d'API, sauvegarde, SSH, CI…" | |
| 129 | + aria-label="Rechercher" | |
| 130 | + /> | |
| 131 | + <ul className="search-results"> | |
| 132 | + {index === null && <li className="search-empty">Chargement…</li>} | |
| 133 | + {index && query.trim().length > 1 && results.length === 0 && <li className="search-empty">Aucun résultat pour « {query.trim()} ».</li>} | |
| 134 | + {results.map((r, i) => ( | |
| 135 | + <li key={r.entry.href + (r.anchor ?? "")}> | |
| 136 | + <button type="button" className={i === cursor ? "active" : ""} onMouseEnter={() => setCursor(i)} onClick={() => go(r)}> | |
| 137 | + <span className="search-where">{r.entry.section}</span> | |
| 138 | + <span className="search-title"> | |
| 139 | + {r.entry.title} | |
| 140 | + {r.anchor && <span className="search-heading"> › {r.entry.headings.find((h) => h.id === r.anchor)?.text}</span>} | |
| 141 | + </span> | |
| 142 | + <span className="search-snippet">{r.snippet}</span> | |
| 143 | + </button> | |
| 144 | + </li> | |
| 145 | + ))} | |
| 146 | + </ul> | |
| 147 | + <p className="search-help"> | |
| 148 | + <kbd>↑</kbd> <kbd>↓</kbd> pour choisir · <kbd>Entrée</kbd> pour ouvrir · <kbd>Échap</kbd> pour fermer | |
| 149 | + </p> | |
| 150 | + </div> | |
| 151 | + </div> | |
| 152 | + )} | |
| 153 | + </> | |
| 154 | + ); | |
| 155 | +} | |
Ajoutésrc/components/ThemeToggle.tsx+44−0
| @@ -0,0 +1,44 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useState } from "react"; | |
| 4 | + | |
| 5 | +type Theme = "light" | "dark"; | |
| 6 | + | |
| 7 | +function currentTheme(): Theme { | |
| 8 | + const set = document.documentElement.dataset.theme; | |
| 9 | + if (set === "light" || set === "dark") return set; | |
| 10 | + return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"; | |
| 11 | +} | |
| 12 | + | |
| 13 | +/** Light / dark switch, kept in this browser (localStorage). */ | |
| 14 | +export function ThemeToggle() { | |
| 15 | + const [theme, setTheme] = useState<Theme | null>(null); | |
| 16 | + useEffect(() => setTheme(currentTheme()), []); | |
| 17 | + | |
| 18 | + const toggle = () => { | |
| 19 | + const next: Theme = currentTheme() === "dark" ? "light" : "dark"; | |
| 20 | + document.documentElement.dataset.theme = next; | |
| 21 | + try { | |
| 22 | + localStorage.setItem("ds_theme", next); | |
| 23 | + } catch { | |
| 24 | + // Storage refused (private browsing): the theme only lasts for this page. | |
| 25 | + } | |
| 26 | + setTheme(next); | |
| 27 | + }; | |
| 28 | + | |
| 29 | + const label = theme === "light" ? "Passer au thème sombre" : "Passer au thème clair"; | |
| 30 | + return ( | |
| 31 | + <button type="button" className="btn icon" onClick={toggle} aria-label={label} title={label}> | |
| 32 | + {theme === "light" ? ( | |
| 33 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"> | |
| 34 | + <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" /> | |
| 35 | + </svg> | |
| 36 | + ) : ( | |
| 37 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"> | |
| 38 | + <circle cx="12" cy="12" r="4" /> | |
| 39 | + <path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" /> | |
| 40 | + </svg> | |
| 41 | + )} | |
| 42 | + </button> | |
| 43 | + ); | |
| 44 | +} | |
Ajoutésrc/lib/content.ts+91−0
| @@ -0,0 +1,91 @@ | ||
| 1 | +import fs from "fs"; | |
| 2 | +import path from "path"; | |
| 3 | +import { CONTENT_VARIABLES, requireUrl } from "./env"; | |
| 4 | +import { renderMarkdown, type RenderedPage } from "./markdown"; | |
| 5 | +import { SECTIONS, sectionById } from "./sections"; | |
| 6 | + | |
| 7 | +// Pages of the documentation: content/<section>/<page>.md, with a header: | |
| 8 | +// --- | |
| 9 | +// title: Authentification | |
| 10 | +// description: Créer une clé et l'envoyer. | |
| 11 | +// order: 2 | |
| 12 | +// --- | |
| 13 | +// "index.md" is the home of the section. {{MAIN_SITE_URL}} and the other names of CONTENT_VARIABLES | |
| 14 | +// are replaced by their value in the .env. | |
| 15 | + | |
| 16 | +const CONTENT_DIR = path.join(process.cwd(), "content"); | |
| 17 | + | |
| 18 | +export interface PageMeta { | |
| 19 | + section: string; | |
| 20 | + slug: string; | |
| 21 | + title: string; | |
| 22 | + description: string; | |
| 23 | + order: number; | |
| 24 | + href: string; | |
| 25 | +} | |
| 26 | + | |
| 27 | +export interface Page extends PageMeta, RenderedPage {} | |
| 28 | + | |
| 29 | +function parseFile(file: string): { meta: Record<string, string>; body: string } { | |
| 30 | + const raw = fs.readFileSync(file, "utf8").replace(/\r\n/g, "\n"); | |
| 31 | + const match = raw.match(/^---\n([\s\S]*?)\n---\n?/); | |
| 32 | + if (!match) throw new Error(`[Contenu] ${file} : en-tête --- manquant`); | |
| 33 | + const meta: Record<string, string> = {}; | |
| 34 | + for (const line of match[1].split("\n")) { | |
| 35 | + const kv = line.match(/^([a-zA-Z]+):\s*(.*)$/); | |
| 36 | + if (kv) meta[kv[1]] = kv[2].trim().replace(/^"(.*)"$/, "$1"); | |
| 37 | + } | |
| 38 | + if (!meta.title) throw new Error(`[Contenu] ${file} : titre manquant`); | |
| 39 | + return { meta, body: raw.slice(match[0].length) }; | |
| 40 | +} | |
| 41 | + | |
| 42 | +function substitute(body: string, file: string): string { | |
| 43 | + return body.replace(/\{\{([A-Z_]+)\}\}/g, (_all, name: string) => { | |
| 44 | + if (!(CONTENT_VARIABLES as readonly string[]).includes(name)) throw new Error(`[Contenu] ${file} : variable inconnue {{${name}}}`); | |
| 45 | + return requireUrl(name); | |
| 46 | + }); | |
| 47 | +} | |
| 48 | + | |
| 49 | +const hrefOf = (section: string, slug: string) => (slug === "index" ? `/${section}/` : `/${section}/${slug}/`); | |
| 50 | + | |
| 51 | +export function listPages(section: string): PageMeta[] { | |
| 52 | + const dir = path.join(CONTENT_DIR, section); | |
| 53 | + if (!sectionById(section) || !fs.existsSync(dir)) return []; | |
| 54 | + return fs | |
| 55 | + .readdirSync(dir) | |
| 56 | + .filter((f) => f.endsWith(".md")) | |
| 57 | + .map((f) => { | |
| 58 | + const slug = f.slice(0, -3); | |
| 59 | + const { meta } = parseFile(path.join(dir, f)); | |
| 60 | + return { | |
| 61 | + section, | |
| 62 | + slug, | |
| 63 | + title: meta.title, | |
| 64 | + description: meta.description ?? "", | |
| 65 | + order: slug === "index" ? -1 : Number(meta.order ?? 999), | |
| 66 | + href: hrefOf(section, slug), | |
| 67 | + }; | |
| 68 | + }) | |
| 69 | + .sort((a, b) => a.order - b.order || a.title.localeCompare(b.title, "fr")); | |
| 70 | +} | |
| 71 | + | |
| 72 | +export async function getPage(section: string, slug: string): Promise<Page | null> { | |
| 73 | + if (!/^[a-z0-9-]+$/.test(slug) || !sectionById(section)) return null; | |
| 74 | + const file = path.join(CONTENT_DIR, section, `${slug}.md`); | |
| 75 | + if (!fs.existsSync(file)) return null; | |
| 76 | + const { meta, body } = parseFile(file); | |
| 77 | + const rendered = await renderMarkdown(substitute(body, file)); | |
| 78 | + return { | |
| 79 | + section, | |
| 80 | + slug, | |
| 81 | + title: meta.title, | |
| 82 | + description: meta.description ?? "", | |
| 83 | + order: Number(meta.order ?? 999), | |
| 84 | + href: hrefOf(section, slug), | |
| 85 | + ...rendered, | |
| 86 | + }; | |
| 87 | +} | |
| 88 | + | |
| 89 | +export function allPages(): PageMeta[] { | |
| 90 | + return SECTIONS.flatMap((s) => listPages(s.id)); | |
| 91 | +} | |
Ajoutésrc/lib/env.ts+17−0
| @@ -0,0 +1,17 @@ | ||
| 1 | +// Settings of the documentation, all from the .env (no default value in the code). | |
| 2 | + | |
| 3 | +export function requireEnv(name: string): string { | |
| 4 | + const value = process.env[name]?.trim(); | |
| 5 | + if (!value) throw new Error(`[Config] Variable ${name} manquante dans le .env`); | |
| 6 | + return value; | |
| 7 | +} | |
| 8 | + | |
| 9 | +/** An address of the .env, without its final slash. */ | |
| 10 | +export function requireUrl(name: string): string { | |
| 11 | + const value = requireEnv(name).replace(/\/+$/, ""); | |
| 12 | + new URL(value); | |
| 13 | + return value; | |
| 14 | +} | |
| 15 | + | |
| 16 | +/** Names usable in the Markdown pages as {{NAME}} (replaced at build time). */ | |
| 17 | +export const CONTENT_VARIABLES = ["PUBLIC_URL", "MAIN_SITE_URL", "GIT_URL", "STATUS_URL"] as const; | |
Ajoutésrc/lib/markdown.ts+119−0
| @@ -0,0 +1,119 @@ | ||
| 1 | +import { Marked, type Tokens } from "marked"; | |
| 2 | +import { createHighlighter, type Highlighter } from "shiki"; | |
| 3 | + | |
| 4 | +// Markdown of the pages (content/, written by Dimdam's: trusted) to HTML, at build time: | |
| 5 | +// headings with an anchor, code colored by Shiki (light and dark), callouts "> [!NOTE]". | |
| 6 | + | |
| 7 | +export interface TocEntry { | |
| 8 | + id: string; | |
| 9 | + text: string; | |
| 10 | + depth: 2 | 3; | |
| 11 | +} | |
| 12 | + | |
| 13 | +export interface RenderedPage { | |
| 14 | + html: string; | |
| 15 | + toc: TocEntry[]; | |
| 16 | + /** Plain text, for the search index. */ | |
| 17 | + text: string; | |
| 18 | +} | |
| 19 | + | |
| 20 | +const LANGS = ["bash", "shell", "json", "javascript", "typescript", "yaml", "python", "ini", "diff", "http", "dockerfile", "text"]; | |
| 21 | +const ALIASES: Record<string, string> = { sh: "bash", js: "javascript", ts: "typescript", yml: "yaml", console: "bash", env: "ini" }; | |
| 22 | +const CALLOUTS: Record<string, string> = { NOTE: "Note", TIP: "Astuce", WARNING: "Attention", DANGER: "Important" }; | |
| 23 | + | |
| 24 | +let highlighter: Promise<Highlighter> | null = null; | |
| 25 | +function getHighlighter() { | |
| 26 | + highlighter ??= createHighlighter({ themes: ["github-light", "github-dark"], langs: LANGS }); | |
| 27 | + return highlighter; | |
| 28 | +} | |
| 29 | + | |
| 30 | +export function slugify(text: string): string { | |
| 31 | + return text | |
| 32 | + .normalize("NFD") | |
| 33 | + .replace(/[̀-ͯ]/g, "") | |
| 34 | + .toLowerCase() | |
| 35 | + .replace(/<[^>]+>/g, "") | |
| 36 | + .replace(/[^a-z0-9]+/g, "-") | |
| 37 | + .replace(/^-+|-+$/g, ""); | |
| 38 | +} | |
| 39 | + | |
| 40 | +const escapeHtml = (s: string) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); | |
| 41 | +const stripTags = (s: string) => s.replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'"); | |
| 42 | + | |
| 43 | +type CodeToken = Tokens.Code & { highlighted?: string }; | |
| 44 | +type QuoteToken = Tokens.Blockquote & { callout?: string }; | |
| 45 | + | |
| 46 | +export async function renderMarkdown(markdown: string): Promise<RenderedPage> { | |
| 47 | + const hl = await getHighlighter(); | |
| 48 | + const toc: TocEntry[] = []; | |
| 49 | + const used = new Map<string, number>(); | |
| 50 | + const uniqueId = (text: string) => { | |
| 51 | + const base = slugify(text) || "section"; | |
| 52 | + const n = used.get(base) ?? 0; | |
| 53 | + used.set(base, n + 1); | |
| 54 | + return n ? `${base}-${n}` : base; | |
| 55 | + }; | |
| 56 | + | |
| 57 | + const marked = new Marked({ gfm: true, async: true }); | |
| 58 | + marked.use({ | |
| 59 | + walkTokens(token) { | |
| 60 | + if (token.type === "code") { | |
| 61 | + const code = token as CodeToken; | |
| 62 | + const lang = ALIASES[code.lang ?? ""] ?? (LANGS.includes(code.lang ?? "") ? code.lang! : "text"); | |
| 63 | + code.highlighted = hl.codeToHtml(code.text, { lang, themes: { light: "github-light", dark: "github-dark" }, defaultColor: false }); | |
| 64 | + } | |
| 65 | + if (token.type === "blockquote") { | |
| 66 | + const quote = token as QuoteToken; | |
| 67 | + const first = quote.tokens[0]; | |
| 68 | + if (first?.type === "paragraph") { | |
| 69 | + const marker = first.text.match(/^\[!(NOTE|TIP|WARNING|DANGER)\]\s*/); | |
| 70 | + if (marker) { | |
| 71 | + quote.callout = marker[1]; | |
| 72 | + first.text = first.text.slice(marker[0].length); | |
| 73 | + const lead = first.tokens?.[0]; | |
| 74 | + if (lead && "text" in lead) { | |
| 75 | + lead.text = lead.text.replace(/^\[!(NOTE|TIP|WARNING|DANGER)\]\s*/, ""); | |
| 76 | + if ("raw" in lead) lead.raw = lead.text; | |
| 77 | + } | |
| 78 | + } | |
| 79 | + } | |
| 80 | + } | |
| 81 | + }, | |
| 82 | + renderer: { | |
| 83 | + heading({ tokens, depth }) { | |
| 84 | + const inner = this.parser.parseInline(tokens); | |
| 85 | + const id = uniqueId(stripTags(inner)); | |
| 86 | + if (depth === 2 || depth === 3) toc.push({ id, text: stripTags(inner), depth }); | |
| 87 | + return `<h${depth} id="${id}"><a class="anchor" href="#${id}" aria-hidden="true" tabindex="-1">#</a>${inner}</h${depth}>\n`; | |
| 88 | + }, | |
| 89 | + code(token) { | |
| 90 | + const code = token as CodeToken; | |
| 91 | + const label = code.lang ? `<span class="code-lang">${escapeHtml(code.lang)}</span>` : ""; | |
| 92 | + return `<div class="code">${label}<button type="button" class="code-copy" aria-label="Copier le code">Copier</button>${code.highlighted ?? `<pre><code>${escapeHtml(code.text)}</code></pre>`}</div>\n`; | |
| 93 | + }, | |
| 94 | + blockquote(token) { | |
| 95 | + const quote = token as QuoteToken; | |
| 96 | + const body = this.parser.parse(quote.tokens); | |
| 97 | + if (!quote.callout) return `<blockquote>${body}</blockquote>\n`; | |
| 98 | + return `<div class="callout ${quote.callout.toLowerCase()}"><p class="callout-title">${CALLOUTS[quote.callout]}</p>${body}</div>\n`; | |
| 99 | + }, | |
| 100 | + table(token) { | |
| 101 | + const head = token.header.map((cell) => `<th${cell.align ? ` style="text-align:${cell.align}"` : ""}>${this.parser.parseInline(cell.tokens)}</th>`).join(""); | |
| 102 | + const rows = token.rows | |
| 103 | + .map((row) => `<tr>${row.map((cell) => `<td${cell.align ? ` style="text-align:${cell.align}"` : ""}>${this.parser.parseInline(cell.tokens)}</td>`).join("")}</tr>`) | |
| 104 | + .join(""); | |
| 105 | + return `<div class="table"><table><thead><tr>${head}</tr></thead><tbody>${rows}</tbody></table></div>\n`; | |
| 106 | + }, | |
| 107 | + link({ href, title, tokens }) { | |
| 108 | + const inner = this.parser.parseInline(tokens); | |
| 109 | + const external = /^https?:\/\//.test(href); | |
| 110 | + const attrs = `${title ? ` title="${escapeHtml(title)}"` : ""}${external ? ' target="_blank" rel="noopener noreferrer"' : ""}`; | |
| 111 | + return `<a href="${escapeHtml(href)}"${attrs}>${inner}</a>`; | |
| 112 | + }, | |
| 113 | + }, | |
| 114 | + }); | |
| 115 | + | |
| 116 | + const html = await marked.parse(markdown); | |
| 117 | + const text = stripTags(html).replace(/\s+/g, " ").trim(); | |
| 118 | + return { html, toc, text }; | |
| 119 | +} | |
Ajoutésrc/lib/sections.ts+32−0
| @@ -0,0 +1,32 @@ | ||
| 1 | +// The documentations, one per product: a tab in the header and a folder in content/. | |
| 2 | + | |
| 3 | +export interface Section { | |
| 4 | + id: string; | |
| 5 | + title: string; | |
| 6 | + description: string; | |
| 7 | +} | |
| 8 | + | |
| 9 | +export const SECTIONS: Section[] = [ | |
| 10 | + { | |
| 11 | + id: "hebergement", | |
| 12 | + title: "Hébergement", | |
| 13 | + description: "Commander, configurer et gérer vos services : sites web, bots, serveurs de jeu, VPS et bases de données.", | |
| 14 | + }, | |
| 15 | + { | |
| 16 | + id: "api", | |
| 17 | + title: "API & CLI", | |
| 18 | + description: "Piloter vos services depuis vos scripts et vos pipelines avec l'API REST et la ligne de commande dimdams.", | |
| 19 | + }, | |
| 20 | + { | |
| 21 | + id: "git", | |
| 22 | + title: "Dimdam's Git", | |
| 23 | + description: "Héberger vos dépôts en France : code, collaboration, CI, paquets et sites Pages.", | |
| 24 | + }, | |
| 25 | + { | |
| 26 | + id: "status", | |
| 27 | + title: "Dimdam's Status", | |
| 28 | + description: "Suivre l'état des services Dimdam's et l'intégrer dans vos outils.", | |
| 29 | + }, | |
| 30 | +]; | |
| 31 | + | |
| 32 | +export const sectionById = (id: string) => SECTIONS.find((s) => s.id === id); | |
Ajoutétsconfig.json+21−0
| @@ -0,0 +1,21 @@ | ||
| 1 | +{ | |
| 2 | + "compilerOptions": { | |
| 3 | + "target": "ES2022", | |
| 4 | + "lib": ["dom", "dom.iterable", "esnext"], | |
| 5 | + "allowJs": false, | |
| 6 | + "skipLibCheck": true, | |
| 7 | + "strict": true, | |
| 8 | + "noEmit": true, | |
| 9 | + "esModuleInterop": true, | |
| 10 | + "module": "esnext", | |
| 11 | + "moduleResolution": "bundler", | |
| 12 | + "resolveJsonModule": true, | |
| 13 | + "isolatedModules": true, | |
| 14 | + "jsx": "react-jsx", | |
| 15 | + "incremental": true, | |
| 16 | + "plugins": [{ "name": "next" }], | |
| 17 | + "paths": { "@/*": ["./src/*"] } | |
| 18 | + }, | |
| 19 | + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"], | |
| 20 | + "exclude": ["node_modules"] | |
| 21 | +} | |