Add chat interface and implement dark mode throughout the app
This commit is contained in:
parent
f6223414da
commit
d7d67b274b
@ -1,32 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Fondations de rédaction et de comportement
|
||||
|
||||
[portée]
|
||||
S’applique à tout le dépôt 4NK/4NK_node pour toute génération, refactorisation, édition inline ou discussion dans Cursor.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Garantir l’usage exclusif du français.
|
||||
- Proscrire l’injection d’exemples de code applicatif dans la base de code.
|
||||
- Assurer une cohérence stricte de terminologie et de ton.
|
||||
- Exiger une introduction et/ou une conclusion dans toute proposition de texte.
|
||||
|
||||
[directives]
|
||||
|
||||
- Toujours répondre et documenter en français.
|
||||
- Ne pas inclure d’exemples exécutables ou de quickstarts dans la base ; préférer des descriptions prescriptives.
|
||||
- Tout contenu produit doit mentionner explicitement les artefacts à mettre à jour lorsqu’il impacte docs/ et tests/.
|
||||
- Préserver la typographie française (capitaliser uniquement le premier mot d’un titre et les noms propres).
|
||||
|
||||
[validations]
|
||||
|
||||
- Relecture linguistique et technique systématique.
|
||||
- Refuser toute sortie avec exemples de code applicatif.
|
||||
- Vérifier que l’issue traitée se conclut par un rappel des fichiers à mettre à jour.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- README.md, docs/**, tests/**, CHANGELOG.md, .gitea/**.
|
||||
@ -1,72 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Structure projet 4NK_node
|
||||
|
||||
[portée]
|
||||
Maintenance de l’arborescence canonique, création/mise à jour/suppression de fichiers et répertoires.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Garantir l’alignement strict avec l’arborescence 4NK_node.
|
||||
- Prévenir toute dérive structurelle.
|
||||
|
||||
[directives]
|
||||
|
||||
- S’assurer que l’arborescence suivante existe et reste conforme :
|
||||
|
||||
4NK/4NK_node
|
||||
├── archive
|
||||
├── CHANGELOG.md
|
||||
├── CODE_OF_CONDUCT.md
|
||||
├── CONTRIBUTING.md
|
||||
├── docker-compose.yml
|
||||
├── docs
|
||||
│ ├── API.md
|
||||
│ ├── ARCHITECTURE.md
|
||||
│ ├── COMMUNITY_GUIDE.md
|
||||
│ ├── CONFIGURATION.md
|
||||
│ ├── GITEA_SETUP.md
|
||||
│ ├── INDEX.md
|
||||
│ ├── INSTALLATION.md
|
||||
│ ├── MIGRATION.md
|
||||
│ ├── OPEN_SOURCE_CHECKLIST.md
|
||||
│ ├── QUICK_REFERENCE.md
|
||||
│ ├── RELEASE_PLAN.md
|
||||
│ ├── ROADMAP.md
|
||||
│ ├── SECURITY_AUDIT.md
|
||||
│ ├── TESTING.md
|
||||
│ └── USAGE.md
|
||||
├── LICENSE
|
||||
├── README.md
|
||||
├── tests
|
||||
│ ├── cleanup.sh
|
||||
│ ├── connectivity
|
||||
│ ├── external
|
||||
│ ├── integration
|
||||
│ ├── logs
|
||||
│ ├── performance
|
||||
│ ├── README.md
|
||||
│ ├── reports
|
||||
│ └── unit
|
||||
└── .gitea
|
||||
├── ISSUE_TEMPLATE
|
||||
│ ├── bug_report.md
|
||||
│ └── feature_request.md
|
||||
├── PULL_REQUEST_TEMPLATE.md
|
||||
└── workflows
|
||||
└── ci.yml
|
||||
|
||||
- Tout document obsolète est déplacé vers archive/ avec métadonnées (date, raison).
|
||||
- Interdire la suppression brute de fichiers sans archivage et note dans CHANGELOG.md.
|
||||
|
||||
[validations]
|
||||
|
||||
- Diff structurel comparé à cette référence.
|
||||
- Erreur bloquante si un fichier « requis » manque.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- archive/**, docs/**, tests/**, .gitea/**, CHANGELOG.md.
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Documentation continue
|
||||
|
||||
[portée]
|
||||
Mises à jour de docs/** corrélées à tout changement de code, configuration, dépendance, données ou CI.
|
||||
|
||||
[objectifs]
|
||||
- Remplacer toute section générique « RESUME » par des mises à jour ciblées dans les fichiers appropriés.
|
||||
- Tenir INDEX.md comme table des matières de référence.
|
||||
|
||||
[directives]
|
||||
- À chaque changement, mettre à jour :
|
||||
- API.md (spécifications, contrats, schémas, invariants).
|
||||
- ARCHITECTURE.md (décisions, diagrammes, couplages, performances).
|
||||
- CONFIGURATION.md (paramètres, formats, valeurs par défaut).
|
||||
- INSTALLATION.md (pré-requis, étapes, vérifications).
|
||||
- MIGRATION.md (chemins de migration, scripts, compatibilités).
|
||||
- USAGE.md (parcours fonctionnels, contraintes).
|
||||
- TESTING.md (pyramide, critères d’acceptation).
|
||||
- SECURITY_AUDIT.md (menaces, contrôles, dettes résiduelles).
|
||||
- RELEASE_PLAN.md, ROADMAP.md (planification), OPEN_SOURCE_CHECKLIST.md, COMMUNITY_GUIDE.md, GITEA_SETUP.md.
|
||||
- Maintenir QUICK_REFERENCE.md pour les référentiels synthétiques utilisés par l’équipe.
|
||||
- Ajouter un REX technique en cas d’hypothèses multiples avant résolution dans archive/.
|
||||
|
||||
[validations]
|
||||
- Cohérence croisée entre README.md et INDEX.md.
|
||||
- Refus si une modification de code n’a pas de trace dans docs/** correspondants.
|
||||
|
||||
[artefacts concernés]
|
||||
- docs/**, README.md, archive/**.
|
||||
@ -1,57 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Tests et qualité
|
||||
|
||||
[portée]
|
||||
Stratégie de tests, exécution locale, stabilité, non-régression.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Exiger des tests verts avant tout commit.
|
||||
- Couvrir les axes unit, integration, connectivity, performance, external.
|
||||
|
||||
[directives]
|
||||
|
||||
- Ajouter/mettre à jour des tests dans tests/unit, tests/integration, tests/connectivity, tests/performance, tests/external selon l’impact.
|
||||
- Consigner les journaux dans tests/logs et les rapports dans tests/reports.
|
||||
- Maintenir tests/README.md (stratégie, outillage, seuils).
|
||||
- Fournir un nettoyage reproductible via tests/cleanup.sh.
|
||||
- Bloquer l’édition si des tests échouent tant que la correction n’est pas appliquée.
|
||||
|
||||
[validations]
|
||||
|
||||
- Refus d’un commit si tests en échec.
|
||||
- Exiger justification et plan de test dans docs/TESTING.md pour toute refonte majeure.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- tests/**, docs/TESTING.md, CHANGELOG.md.
|
||||
|
||||
# Tests et qualité
|
||||
|
||||
[portée]
|
||||
Stratégie de tests, exécution locale, stabilité, non-régression.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Exiger des tests verts avant tout commit.
|
||||
- Couvrir les axes unit, integration, connectivity, performance, external.
|
||||
|
||||
[directives]
|
||||
|
||||
- Ajouter/mettre à jour des tests dans tests/unit, tests/integration, tests/connectivity, tests/performance, tests/external selon l’impact.
|
||||
- Consigner les journaux dans tests/logs et les rapports dans tests/reports.
|
||||
- Maintenir tests/README.md (stratégie, outillage, seuils).
|
||||
- Fournir un nettoyage reproductible via tests/cleanup.sh.
|
||||
- Bloquer l’édition si des tests échouent tant que la correction n’est pas appliquée.
|
||||
|
||||
[validations]
|
||||
|
||||
- Refus d’un commit si tests en échec.
|
||||
- Exiger justification et plan de test dans docs/TESTING.md pour toute refonte majeure.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- tests/**, docs/TESTING.md, CHANGELOG.md.
|
||||
@ -1,55 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Dépendances, compilation et build
|
||||
|
||||
[portée]
|
||||
Gestion des dépendances, compilation fréquente, politique de versions.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Ajouter automatiquement les dépendances manquantes si justifié.
|
||||
- Rechercher systématiquement les dernières versions stables.
|
||||
|
||||
[directives]
|
||||
|
||||
- Lorsqu’une fonctionnalité nécessite une dépendance, l’ajouter et la documenter (nom, version, portée, impact) dans docs/ARCHITECTURE.md et docs/CONFIGURATION.md si nécessaire.
|
||||
- Compiler très régulièrement et « quand nécessaire » (avant refactor, avant push, après mise à jour de dépendances).
|
||||
- Corriger toute erreur de compilation/exécution avant de poursuivre.
|
||||
- Documenter tout changement de dépendances (raison, risques, rollback).
|
||||
|
||||
[validations]
|
||||
|
||||
- Interdire la progression si la compilation échoue.
|
||||
- Vérifier la présence d’une note de changement dans CHANGELOG.md en cas de dépendance ajoutée/retirée.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- docs/ARCHITECTURE.md, docs/CONFIGURATION.md, CHANGELOG.md.
|
||||
|
||||
# Dépendances, compilation et build
|
||||
|
||||
[portée]
|
||||
Gestion des dépendances, compilation fréquente, politique de versions.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Ajouter automatiquement les dépendances manquantes si justifié.
|
||||
- Rechercher systématiquement les dernières versions stables.
|
||||
|
||||
[directives]
|
||||
|
||||
- Lorsqu’une fonctionnalité nécessite une dépendance, l’ajouter et la documenter (nom, version, portée, impact) dans docs/ARCHITECTURE.md et docs/CONFIGURATION.md si nécessaire.
|
||||
- Compiler très régulièrement et « quand nécessaire » (avant refactor, avant push, après mise à jour de dépendances).
|
||||
- Corriger toute erreur de compilation/exécution avant de poursuivre.
|
||||
- Documenter tout changement de dépendances (raison, risques, rollback).
|
||||
|
||||
[validations]
|
||||
|
||||
- Interdire la progression si la compilation échoue.
|
||||
- Vérifier la présence d’une note de changement dans CHANGELOG.md en cas de dépendance ajoutée/retirée.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- docs/ARCHITECTURE.md, docs/CONFIGURATION.md, CHANGELOG.md.
|
||||
@ -1,65 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Automatisation SSH et scripts
|
||||
|
||||
[portée]
|
||||
Création, usage et vérification du dossier scripts/ et de ses trois scripts standards liés aux opérations SSH et CI.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Garantir la présence de scripts/ avec auto-ssh-push.sh, init-ssh-env.sh, setup-ssh-ci.sh.
|
||||
- Encadrer l’usage de ces scripts (locaux et CI), la sécurité, l’idempotence et la traçabilité.
|
||||
- Documenter toute mise à jour dans docs/SSH_UPDATE.md et CHANGELOG.md.
|
||||
|
||||
[directives]
|
||||
|
||||
- Créer et maintenir `scripts/auto-ssh-push.sh`, `scripts/init-ssh-env.sh`, `scripts/setup-ssh-ci.sh`.
|
||||
- Exiger permissions d’exécution adaptées sur scripts/ (exécution locale et CI).
|
||||
- Interdire le stockage de clés privées ou secrets en clair dans le dépôt.
|
||||
- Utiliser des variables d’environnement et secrets CI pour toute donnée sensible.
|
||||
- Rendre chaque script idempotent et verbosable ; produire un code de sortie non-zéro en cas d’échec.
|
||||
- Tracer les opérations : consigner un résumé dans docs/SSH_UPDATE.md (objectif, variables requises, effets, points d’échec).
|
||||
- Ajouter un contrôle automatique dans la CI pour vérifier l’existence et l’exécutabilité de ces scripts.
|
||||
|
||||
[validations]
|
||||
|
||||
- Échec bloquant si un des trois scripts manque ou n’est pas exécutable.
|
||||
- Échec bloquant si docs/SSH_UPDATE.md n’est pas mis à jour lors d’une modification de scripts.
|
||||
- Échec bloquant si un secret attendu n’est pas fourni en CI.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- scripts/**, docs/SSH_UPDATE.md, .gitea/workflows/ci.yml, CHANGELOG.md, docs/CONFIGURATION.md.
|
||||
|
||||
# Automatisation SSH et scripts
|
||||
|
||||
[portée]
|
||||
Création, usage et vérification du dossier scripts/ et de ses trois scripts standards liés aux opérations SSH et CI.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Garantir la présence de scripts/ avec auto-ssh-push.sh, init-ssh-env.sh, setup-ssh-ci.sh.
|
||||
- Encadrer l’usage de ces scripts (locaux et CI), la sécurité, l’idempotence et la traçabilité.
|
||||
- Documenter toute mise à jour dans docs/SSH_UPDATE.md et CHANGELOG.md.
|
||||
|
||||
[directives]
|
||||
|
||||
- Créer et maintenir `scripts/auto-ssh-push.sh`, `scripts/init-ssh-env.sh`, `scripts/setup-ssh-ci.sh`.
|
||||
- Exiger permissions d’exécution adaptées sur scripts/ (exécution locale et CI).
|
||||
- Interdire le stockage de clés privées ou secrets en clair dans le dépôt.
|
||||
- Utiliser des variables d’environnement et secrets CI pour toute donnée sensible.
|
||||
- Rendre chaque script idempotent et verbosable ; produire un code de sortie non-zéro en cas d’échec.
|
||||
- Tracer les opérations : consigner un résumé dans docs/SSH_UPDATE.md (objectif, variables requises, effets, points d’échec).
|
||||
- Ajouter un contrôle automatique dans la CI pour vérifier l’existence et l’exécutabilité de ces scripts.
|
||||
|
||||
[validations]
|
||||
|
||||
- Échec bloquant si un des trois scripts manque ou n’est pas exécutable.
|
||||
- Échec bloquant si docs/SSH_UPDATE.md n’est pas mis à jour lors d’une modification de scripts.
|
||||
- Échec bloquant si un secret attendu n’est pas fourni en CI.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- scripts/**, docs/SSH_UPDATE.md, .gitea/workflows/ci.yml, CHANGELOG.md, docs/CONFIGURATION.md.
|
||||
@ -1,53 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Synchronisation de template (4NK)
|
||||
|
||||
[portée]
|
||||
Tous les projets issus de 4NK_project_template. Contrôle de l’alignement sur .cursor/, .gitea/, AGENTS.md, scripts/, docs/SSH_UPDATE.md.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Garantir l’absence de dérive sur les éléments normatifs.
|
||||
- Exiger la mise à jour documentaire et du changelog à chaque synchronisation.
|
||||
- Bloquer la progression en cas d’intégrité non conforme.
|
||||
|
||||
[directives]
|
||||
- Lire la configuration de .4nk-sync.yml (source_repo, ref, paths, policy).
|
||||
- Refuser toute modification locale dans le périmètre des paths sans PR de synchronisation.
|
||||
- Après synchronisation : exiger mises à jour de CHANGELOG.md et docs/INDEX.md.
|
||||
- Scripts : vérifier présence, permissions d’exécution et absence de secrets en clair.
|
||||
- SSH : exiger mise à jour de docs/SSH_UPDATE.md si scripts/** modifié.
|
||||
|
||||
[validations]
|
||||
- Erreur bloquante si manifest_checksum manquant ou invalide.
|
||||
- Erreur bloquante si un path requis n’existe pas après sync.
|
||||
- Erreur bloquante si tests/CI signalent des scripts non exécutables ou des fichiers sensibles.
|
||||
|
||||
[artefacts concernés]
|
||||
- .4nk-sync.yml, TEMPLATE_VERSION, .cursor/**, .gitea/**, AGENTS.md, scripts/**, docs/SSH_UPDATE.md, CHANGELOG.md.
|
||||
# Synchronisation de template (4NK)
|
||||
|
||||
[portée]
|
||||
Tous les projets issus de 4NK_project_template. Contrôle de l’alignement sur .cursor/, .gitea/, AGENTS.md, scripts/, docs/SSH_UPDATE.md.
|
||||
|
||||
[objectifs]
|
||||
- Garantir l’absence de dérive sur les éléments normatifs.
|
||||
- Exiger la mise à jour documentaire et du changelog à chaque synchronisation.
|
||||
- Bloquer la progression en cas d’intégrité non conforme.
|
||||
|
||||
[directives]
|
||||
- Lire la configuration de .4nk-sync.yml (source_repo, ref, paths, policy).
|
||||
- Refuser toute modification locale dans le périmètre des paths sans PR de synchronisation.
|
||||
- Après synchronisation : exiger mises à jour de CHANGELOG.md et docs/INDEX.md.
|
||||
- Scripts : vérifier présence, permissions d’exécution et absence de secrets en clair.
|
||||
- SSH : exiger mise à jour de docs/SSH_UPDATE.md si scripts/** modifié.
|
||||
|
||||
[validations]
|
||||
- Erreur bloquante si manifest_checksum manquant ou invalide.
|
||||
- Erreur bloquante si un path requis n’existe pas après sync.
|
||||
- Erreur bloquante si tests/CI signalent des scripts non exécutables ou des fichiers sensibles.
|
||||
|
||||
[artefacts concernés]
|
||||
- .4nk-sync.yml, TEMPLATE_VERSION, .cursor/**, .gitea/**, AGENTS.md, scripts/**, docs/SSH_UPDATE.md, CHANGELOG.md.
|
||||
@ -1,156 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
# cursor.mcd — règles d’or 4NK
|
||||
language: fr
|
||||
policies:
|
||||
respond_in_french: true
|
||||
no_examples_in_codebase: true
|
||||
ask_before_push_or_tag: true
|
||||
|
||||
directories:
|
||||
ensure:
|
||||
- archive/
|
||||
- docs/
|
||||
- tests/
|
||||
- .gitea/
|
||||
docs:
|
||||
required_files:
|
||||
- API.md
|
||||
- ARCHITECTURE.md
|
||||
- COMMUNITY_GUIDE.md
|
||||
- CONFIGURATION.md
|
||||
- GITEA_SETUP.md
|
||||
- INDEX.md
|
||||
- INSTALLATION.md
|
||||
- MIGRATION.md
|
||||
- OPEN_SOURCE_CHECKLIST.md
|
||||
- QUICK_REFERENCE.md
|
||||
- RELEASE_PLAN.md
|
||||
- ROADMAP.md
|
||||
- SECURITY_AUDIT.md
|
||||
- TESTING.md
|
||||
- USAGE.md
|
||||
tests:
|
||||
required_files:
|
||||
- cleanup.sh
|
||||
- README.md
|
||||
required_dirs:
|
||||
- connectivity
|
||||
- external
|
||||
- integration
|
||||
- logs
|
||||
- performance
|
||||
- reports
|
||||
- unit
|
||||
gitea:
|
||||
required_files:
|
||||
- PULL_REQUEST_TEMPLATE.md
|
||||
required_dirs:
|
||||
- ISSUE_TEMPLATE
|
||||
- workflows
|
||||
ISSUE_TEMPLATE:
|
||||
required_files:
|
||||
- bug_report.md
|
||||
- feature_request.md
|
||||
workflows:
|
||||
required_files:
|
||||
- ci.yml
|
||||
|
||||
files:
|
||||
required_root_files:
|
||||
- CHANGELOG.md
|
||||
- CODE_OF_CONDUCT.md
|
||||
- CONTRIBUTING.md
|
||||
- docker-compose.yml
|
||||
- LICENSE
|
||||
- README.md
|
||||
|
||||
documentation:
|
||||
update_on:
|
||||
- feature_added
|
||||
- feature_modified
|
||||
- feature_removed
|
||||
- feature_discovered
|
||||
replace_sections_named: ["RESUME"]
|
||||
rex_required_on_multiple_hypotheses: true
|
||||
archive_obsolete_docs: true
|
||||
|
||||
compilation:
|
||||
compile_often: true
|
||||
compile_when_needed: true
|
||||
fail_on_errors: true
|
||||
|
||||
problem_solving:
|
||||
auto_run_steps:
|
||||
- minimal_repro
|
||||
- inspect_logs
|
||||
- bisect_changes
|
||||
- form_hypotheses
|
||||
- targeted_tests
|
||||
- implement_fix
|
||||
- non_regression
|
||||
|
||||
office_docs:
|
||||
docx_reader: docx2txt
|
||||
fallback:
|
||||
- pandoc_convert
|
||||
- request_alternate_source
|
||||
|
||||
dependencies:
|
||||
auto_add_missing: true
|
||||
always_check_latest_stable: true
|
||||
document_changes_in_docs: true
|
||||
|
||||
csv_models:
|
||||
treat_as_source_of_truth: true
|
||||
multirow_headers_supported: true
|
||||
confirm_in_docs: true
|
||||
require_column_definitions: true
|
||||
|
||||
file_processing:
|
||||
study_each_file: true
|
||||
ask_questions_if_needed: true
|
||||
adapt_code_if_needed: true
|
||||
propose_solution_if_unreadable: true
|
||||
|
||||
types_and_properties:
|
||||
auto_correct_incoherences: true
|
||||
document_transformations: true
|
||||
|
||||
functional_consistency:
|
||||
always_ask_clarifying_questions: true
|
||||
|
||||
frontend_architecture:
|
||||
react_code_splitting: true
|
||||
state_management: ["redux", "context_api"]
|
||||
data_service_abstraction: true
|
||||
|
||||
execution_discipline:
|
||||
finish_started_work: true
|
||||
|
||||
open_source_and_gitea:
|
||||
prepare_every_project: true
|
||||
gitea_remote: "git.4nkweb.com"
|
||||
required_files:
|
||||
- LICENSE
|
||||
- CONTRIBUTING.md
|
||||
- CHANGELOG.md
|
||||
- CODE_OF_CONDUCT.md
|
||||
align_with_4NK_node_on_creation: true
|
||||
keep_alignment_updated: true
|
||||
|
||||
tests_and_docs:
|
||||
update_docs_and_tests_with_code: true
|
||||
require_green_tests_before_commit: true
|
||||
|
||||
versioning:
|
||||
manage_with_changelog: true
|
||||
confirm_before_push: true
|
||||
confirm_before_tag: true
|
||||
propose_semver_bump: true
|
||||
|
||||
pre_commit:
|
||||
run_all_tests: true
|
||||
block_on_errors: true
|
||||
|
||||
---
|
||||
@ -1,54 +0,0 @@
|
||||
---
|
||||
alwaysApply: false
|
||||
---
|
||||
# Modélisation des données à partir de CSV
|
||||
|
||||
[portée]
|
||||
Utilisation des CSV comme base des modèles de données, y compris en-têtes multi-lignes.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Confirmer la structure inférée pour chaque CSV.
|
||||
- Demander une définition formelle de toutes les colonnes.
|
||||
|
||||
[directives]
|
||||
|
||||
- Gérer explicitement les en-têtes multi-lignes (titre principal + sous-colonnes).
|
||||
- Confirmer par écrit dans docs/API.md ou docs/ARCHITECTURE.md : nombre de lignes d’en-tête, mapping colonnes→types, unités, domaines de valeurs, nullabilité, contraintes.
|
||||
- Poser des questions si ambiguïtés ; proposer une normalisation temporaire documentée.
|
||||
- Corriger automatiquement les incohérences de types si une règle de mapping est établie ailleurs et documenter la transformation.
|
||||
|
||||
[validations]
|
||||
|
||||
- Aucune ingestion sans spécification de colonnes validée.
|
||||
- Traçabilité des corrections de types (avant/après) dans docs/ARCHITECTURE.md.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- docs/API.md, docs/ARCHITECTURE.md, docs/USAGE.md.
|
||||
|
||||
# Modélisation des données à partir de CSV
|
||||
|
||||
[portée]
|
||||
Utilisation des CSV comme base des modèles de données, y compris en-têtes multi-lignes.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Confirmer la structure inférée pour chaque CSV.
|
||||
- Demander une définition formelle de toutes les colonnes.
|
||||
|
||||
[directives]
|
||||
|
||||
- Gérer explicitement les en-têtes multi-lignes (titre principal + sous-colonnes).
|
||||
- Confirmer par écrit dans docs/API.md ou docs/ARCHITECTURE.md : nombre de lignes d’en-tête, mapping colonnes→types, unités, domaines de valeurs, nullabilité, contraintes.
|
||||
- Poser des questions si ambiguïtés ; proposer une normalisation temporaire documentée.
|
||||
- Corriger automatiquement les incohérences de types si une règle de mapping est établie ailleurs et documenter la transformation.
|
||||
|
||||
[validations]
|
||||
|
||||
- Aucune ingestion sans spécification de colonnes validée.
|
||||
- Traçabilité des corrections de types (avant/après) dans docs/ARCHITECTURE.md.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- docs/API.md, docs/ARCHITECTURE.md, docs/USAGE.md.
|
||||
@ -1,41 +0,0 @@
|
||||
---
|
||||
alwaysApply: false
|
||||
---
|
||||
# Lecture des documents bureautiques
|
||||
|
||||
[portée]
|
||||
Lecture des fichiers .docx et alternatives.
|
||||
|
||||
[objectifs]
|
||||
- Utiliser docx2txt par défaut.
|
||||
- Proposer des solutions de repli si lecture impossible.
|
||||
|
||||
[directives]
|
||||
- Lire les .docx avec docx2txt.
|
||||
- En cas d’échec, proposer : conversion via pandoc, demande d’une source alternative, ou extraction textuelle.
|
||||
- Documenter dans docs/INDEX.md la provenance et le statut des documents importés.
|
||||
|
||||
[validations]
|
||||
- Vérification que les contenus extraits sont intégrés aux fichiers docs/ concernés.
|
||||
|
||||
[artefacts concernés]
|
||||
- docs/**, archive/**.
|
||||
# Lecture des documents bureautiques
|
||||
|
||||
[portée]
|
||||
Lecture des fichiers .docx et alternatives.
|
||||
|
||||
[objectifs]
|
||||
- Utiliser docx2txt par défaut.
|
||||
- Proposer des solutions de repli si lecture impossible.
|
||||
|
||||
[directives]
|
||||
- Lire les .docx avec docx2txt.
|
||||
- En cas d’échec, proposer : conversion via pandoc, demande d’une source alternative, ou extraction textuelle.
|
||||
- Documenter dans docs/INDEX.md la provenance et le statut des documents importés.
|
||||
|
||||
[validations]
|
||||
- Vérification que les contenus extraits sont intégrés aux fichiers docs/ concernés.
|
||||
|
||||
[artefacts concernés]
|
||||
- docs/**, archive/**.
|
||||
@ -1,56 +0,0 @@
|
||||
---
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Architecture frontend
|
||||
|
||||
[portée]
|
||||
Qualité du bundle, découpage, état global et couche de services.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Réduire la taille du bundle initial via code splitting.
|
||||
- Éviter le prop drilling via Redux ou Context API.
|
||||
- Abstraire les services de données pour testabilité et maintenance.
|
||||
|
||||
[directives]
|
||||
|
||||
- Mettre en place React.lazy et Suspense pour le chargement différé des vues/segments.
|
||||
- Centraliser l’état global via Redux ou Context API.
|
||||
- Isoler les appels « data » derrière une couche d’abstraction à interface stable.
|
||||
- Interdire l’ajout d’exemples front dans la base de code.
|
||||
|
||||
[validations]
|
||||
|
||||
- Vérifier que les points d’entrée sont minimes et que les segments non critiques sont chargés à la demande.
|
||||
- S’assurer que docs/ARCHITECTURE.md décrit les décisions et les points d’extension.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- docs/ARCHITECTURE.md, docs/TESTING.md.
|
||||
# Architecture frontend
|
||||
|
||||
[portée]
|
||||
Qualité du bundle, découpage, état global et couche de services.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Réduire la taille du bundle initial via code splitting.
|
||||
- Éviter le prop drilling via Redux ou Context API.
|
||||
- Abstraire les services de données pour testabilité et maintenance.
|
||||
|
||||
[directives]
|
||||
|
||||
- Mettre en place React.lazy et Suspense pour le chargement différé des vues/segments.
|
||||
- Centraliser l’état global via Redux ou Context API.
|
||||
- Isoler les appels « data » derrière une couche d’abstraction à interface stable.
|
||||
- Interdire l’ajout d’exemples front dans la base de code.
|
||||
|
||||
[validations]
|
||||
|
||||
- Vérifier que les points d’entrée sont minimes et que les segments non critiques sont chargés à la demande.
|
||||
- S’assurer que docs/ARCHITECTURE.md décrit les décisions et les points d’extension.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- docs/ARCHITECTURE.md, docs/TESTING.md.
|
||||
@ -1,53 +0,0 @@
|
||||
---
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Versionnage et publication
|
||||
|
||||
[portée]
|
||||
Gestion sémantique des versions, CHANGELOG, confirmation push/tag.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Tenir CHANGELOG.md comme source unique de vérité.
|
||||
- Demander confirmation avant push et tag.
|
||||
|
||||
[directives]
|
||||
|
||||
- À chaque changement significatif, mettre à jour CHANGELOG.md (ajouts, changements, corrections, ruptures).
|
||||
- Proposer un bump semver (major/minor/patch) motivé par l’impact.
|
||||
- Avant tout push ou tag, demander confirmation explicite.
|
||||
|
||||
[validations]
|
||||
|
||||
- Refus si modification sans entrée correspondante dans CHANGELOG.md.
|
||||
- Cohérence entre CHANGELOG.md, docs/RELEASE_PLAN.md et docs/ROADMAP.md.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- CHANGELOG.md, docs/RELEASE_PLAN.md, docs/ROADMAP.md.
|
||||
|
||||
# Versionnage et publication
|
||||
|
||||
[portée]
|
||||
Gestion sémantique des versions, CHANGELOG, confirmation push/tag.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Tenir CHANGELOG.md comme source unique de vérité.
|
||||
- Demander confirmation avant push et tag.
|
||||
|
||||
[directives]
|
||||
|
||||
- À chaque changement significatif, mettre à jour CHANGELOG.md (ajouts, changements, corrections, ruptures).
|
||||
- Proposer un bump semver (major/minor/patch) motivé par l’impact.
|
||||
- Avant tout push ou tag, demander confirmation explicite.
|
||||
|
||||
[validations]
|
||||
|
||||
- Refus si modification sans entrée correspondante dans CHANGELOG.md.
|
||||
- Cohérence entre CHANGELOG.md, docs/RELEASE_PLAN.md et docs/ROADMAP.md.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- CHANGELOG.md, docs/RELEASE_PLAN.md, docs/ROADMAP.md.
|
||||
@ -1,37 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Garde de release: tests, documentation, compilation, version, changelog, tag
|
||||
|
||||
[portée]
|
||||
Contrôler systématiquement avant push/tag: tests verts, docs mises à jour, build OK, alignement numéro de version ↔ changelog ↔ tag git, mise à jour de déploiement, confirmation utilisateur (latest vs wip).
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Empêcher toute publication sans vérifications minimales.
|
||||
- Exiger la cohérence sémantique (VERSION/TEMPLATE_VERSION ↔ CHANGELOG ↔ tag git).
|
||||
- Demander explicitement « latest » ou « wip » et appliquer la bonne stratégie.
|
||||
|
||||
[directives]
|
||||
|
||||
- Avant push/tag, exécuter: tests, compilation, lints (si configurés).
|
||||
- Mettre à jour la documentation et le changelog en conséquence.
|
||||
- Aligner le fichier de version (VERSION ou TEMPLATE_VERSION), l’entrée CHANGELOG et le tag.
|
||||
- Demander confirmation utilisateur: `latest` (release stable) ou `wip` (travail en cours).
|
||||
- latest: entrée datée dans CHANGELOG, version stable, tag `vX.Y.Z`.
|
||||
- wip: suffixe `-wip` recommandé dans version/tag (ex: `vX.Y.Z-wip.N`).
|
||||
- Mettre à jour le déploiement après publication (si pipeline défini), sinon documenter l’étape.
|
||||
|
||||
[validations]
|
||||
|
||||
- Refuser push/tag si:
|
||||
- tests/compilation échouent,
|
||||
- CHANGELOG non mis à jour,
|
||||
- VERSION/TEMPLATE_VERSION absent ou incohérent,
|
||||
- release type non fourni (ni latest, ni wip).
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- CHANGELOG.md, VERSION ou TEMPLATE_VERSION, docs/**, .gitea/workflows/**, scripts/**.
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Open source et Gitea
|
||||
|
||||
[portée]
|
||||
Conformité open source, templates Gitea, CI.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Préparer chaque projet pour un dépôt Gitea (git.4nkweb.com).
|
||||
- Maintenir les fichiers de gouvernance et la CI.
|
||||
|
||||
[directives]
|
||||
|
||||
- Vérifier la présence et l’actualité de : LICENSE, CONTRIBUTING.md, CODE_OF_CONDUCT.md, OPEN_SOURCE_CHECKLIST.md.
|
||||
- Maintenir .gitea/ :
|
||||
- ISSUE_TEMPLATE/bug_report.md, feature_request.md
|
||||
- PULL_REQUEST_TEMPLATE.md
|
||||
- workflows/ci.yml
|
||||
- Documenter dans docs/GITEA_SETUP.md la configuration distante et les permissions.
|
||||
|
||||
[validations]
|
||||
|
||||
- Refus si un des fichiers « gouvernance/CI » manque.
|
||||
- Cohérence entre docs/OPEN_SOURCE_CHECKLIST.md et l’état du repo.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- .gitea/**, docs/GITEA_SETUP.md, docs/OPEN_SOURCE_CHECKLIST.md.
|
||||
|
||||
# Open source et Gitea
|
||||
|
||||
[portée]
|
||||
Conformité open source, templates Gitea, CI.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Préparer chaque projet pour un dépôt Gitea (git.4nkweb.com).
|
||||
- Maintenir les fichiers de gouvernance et la CI.
|
||||
|
||||
[directives]
|
||||
|
||||
- Vérifier la présence et l’actualité de : LICENSE, CONTRIBUTING.md, CODE_OF_CONDUCT.md, OPEN_SOURCE_CHECKLIST.md.
|
||||
- Maintenir .gitea/ :
|
||||
- ISSUE_TEMPLATE/bug_report.md, feature_request.md
|
||||
- PULL_REQUEST_TEMPLATE.md
|
||||
- workflows/ci.yml
|
||||
- Documenter dans docs/GITEA_SETUP.md la configuration distante et les permissions.
|
||||
|
||||
[validations]
|
||||
|
||||
- Refus si un des fichiers « gouvernance/CI » manque.
|
||||
- Cohérence entre docs/OPEN_SOURCE_CHECKLIST.md et l’état du repo.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- .gitea/**, docs/GITEA_SETUP.md, docs/OPEN_SOURCE_CHECKLIST.md.
|
||||
@ -1,53 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Tri, diagnostic et résolution de problèmes
|
||||
|
||||
[portée]
|
||||
Boucle de triage : reproduction, diagnostic, correctif, non-régression.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Exécuter automatiquement les étapes de résolution.
|
||||
- Bloquer l’avancement tant que les erreurs ne sont pas corrigées.
|
||||
|
||||
[directives]
|
||||
|
||||
- Étapes obligatoires : reproduction minimale, inspection des logs, bissection des changements, formulation d’hypothèses, tests ciblés, correctif, test de non-régression.
|
||||
- Lorsque plusieurs hypothèses ont été testées, produire un REX dans archive/ avec liens vers les commits.
|
||||
- Poser des questions de cohérence fonctionnelle si des ambiguïtés subsistent (contrats d’API, invariants, SLA).
|
||||
|
||||
[validations]
|
||||
|
||||
- Interdiction de clore une tâche si un test échoue ou si une alerte critique subsiste.
|
||||
- Traçabilité du REX si investigations multiples.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- tests/**, archive/**, docs/TESTING.md, docs/ARCHITECTURE.md.
|
||||
|
||||
# Tri, diagnostic et résolution de problèmes
|
||||
|
||||
[portée]
|
||||
Boucle de triage : reproduction, diagnostic, correctif, non-régression.
|
||||
|
||||
[objectifs]
|
||||
|
||||
- Exécuter automatiquement les étapes de résolution.
|
||||
- Bloquer l’avancement tant que les erreurs ne sont pas corrigées.
|
||||
|
||||
[directives]
|
||||
|
||||
- Étapes obligatoires : reproduction minimale, inspection des logs, bissection des changements, formulation d’hypothèses, tests ciblés, correctif, test de non-régression.
|
||||
- Lorsque plusieurs hypothèses ont été testées, produire un REX dans archive/ avec liens vers les commits.
|
||||
- Poser des questions de cohérence fonctionnelle si des ambiguïtés subsistent (contrats d’API, invariants, SLA).
|
||||
|
||||
[validations]
|
||||
|
||||
- Interdiction de clore une tâche si un test échoue ou si une alerte critique subsiste.
|
||||
- Traçabilité du REX si investigations multiples.
|
||||
|
||||
[artefacts concernés]
|
||||
|
||||
- tests/**, archive/**, docs/TESTING.md, docs/ARCHITECTURE.md.
|
||||
@ -1,16 +0,0 @@
|
||||
# Index des règles .cursor/rules
|
||||
|
||||
- 00-foundations.mdc : règles linguistiques et éditoriales (français, pas d’exemples en base, introduction/conclusion).
|
||||
- 10-project-structure.mdc : arborescence canonique 4NK_node et garde-fous.
|
||||
- 20-documentation.mdc : documentation continue, remplacement de « RESUME », INDEX.md.
|
||||
- 30-testing.mdc : tests (unit, integration, connectivity, performance, external), logs/reports.
|
||||
- 40-dependencies-and-build.mdc : dépendances, compilation, corrections bloquantes.
|
||||
- 50-data-csv-models.mdc : CSV avec en-têtes multi-lignes, définition des colonnes.
|
||||
- 60-office-docs.mdc : lecture .docx via docx2txt + repli.
|
||||
- 70-frontend-architecture.mdc : React.lazy/Suspense, état global, couche de services.
|
||||
- 80-versioning-and-release.mdc : CHANGELOG, semver, confirmation push/tag.
|
||||
- 85-release-guard.mdc : garde de release (tests/doc/build/version/changelog/tag; latest vs wip).
|
||||
- 90-gitea-and-oss.mdc : fichiers open source, .gitea, CI, Gitea remote.
|
||||
- 95-triage-and-problem-solving.mdc : boucle de diagnostic, REX, non-régression.
|
||||
|
||||
Ces règles sont conçues pour être ajoutées au contexte de Cursor depuis l’interface (@Cursor Rules) et s’appuient sur le mécanisme de règles projet stockées dans `.cursor/rules/`. :contentReference[oaicite:3]{index=3}
|
||||
@ -1,27 +0,0 @@
|
||||
# Ignorer les contenus volumineux pour le contexte IA
|
||||
node_modules/
|
||||
package-lock.json
|
||||
dist/
|
||||
build/
|
||||
coverage/
|
||||
.cache/
|
||||
.tmp/
|
||||
.parcel-cache/
|
||||
|
||||
# Rapports et logs de tests
|
||||
tests/logs/
|
||||
tests/reports/
|
||||
|
||||
# Fichiers lourds
|
||||
**/*.map
|
||||
**/*.min.*
|
||||
**/*.wasm
|
||||
**/*.{png,jpg,jpeg,svg,ico,pdf}
|
||||
|
||||
# Ne pas ignorer .cursor ni AGENTS.md
|
||||
!/.cursor
|
||||
!/AGENTS.md
|
||||
|
||||
!.cursor/
|
||||
|
||||
!AGENTS.md
|
||||
@ -1,100 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Signaler un bug pour nous aider à améliorer docv
|
||||
title: '[BUG] '
|
||||
labels: ['bug', 'needs-triage']
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
> Ce fichier est un modèle (template). Adaptez les champs à votre projet dérivé.
|
||||
|
||||
## 🐛 Description du Bug
|
||||
|
||||
Description claire et concise du problème.
|
||||
|
||||
## 🔄 Étapes pour Reproduire
|
||||
|
||||
1. Aller à '...'
|
||||
2. Cliquer sur '...'
|
||||
3. Faire défiler jusqu'à '...'
|
||||
4. Voir l'erreur
|
||||
|
||||
## ✅ Comportement Attendu
|
||||
|
||||
Description de ce qui devrait se passer.
|
||||
|
||||
## ❌ Comportement Actuel
|
||||
|
||||
Description de ce qui se passe actuellement.
|
||||
|
||||
## 📸 Capture d'Écran
|
||||
|
||||
Si applicable, ajoutez une capture d'écran pour expliquer votre problème.
|
||||
|
||||
## 💻 Informations Système
|
||||
|
||||
- **OS** : [ex: Ubuntu 20.04, macOS 12.0, Windows 11]
|
||||
- **Docker** : [ex: 20.10.0]
|
||||
- **Docker Compose** : [ex: 2.0.0]
|
||||
- **Version docv** : [ex: v1.0.0]
|
||||
- **Architecture** : [ex: x86_64, ARM64]
|
||||
|
||||
## 📋 Configuration
|
||||
|
||||
### Services Actifs
|
||||
```bash
|
||||
docker ps
|
||||
```
|
||||
|
||||
### Variables d'Environnement
|
||||
```bash
|
||||
# Bitcoin Core
|
||||
BITCOIN_NETWORK=signet
|
||||
BITCOIN_RPC_PORT=18443
|
||||
|
||||
# Blindbit
|
||||
BLINDBIT_PORT=8000
|
||||
|
||||
# SDK Relay
|
||||
SDK_RELAY_PORTS=8090-8095
|
||||
```
|
||||
|
||||
## 📝 Logs
|
||||
|
||||
### Logs Pertinents
|
||||
```
|
||||
Logs pertinents ici
|
||||
```
|
||||
|
||||
### Logs d'Erreur
|
||||
```
|
||||
Logs d'erreur ici
|
||||
```
|
||||
|
||||
### Logs de Debug
|
||||
```
|
||||
Logs de debug ici (si RUST_LOG=debug)
|
||||
```
|
||||
|
||||
## 🔧 Tentatives de Résolution
|
||||
|
||||
- [ ] Redémarrage des services
|
||||
- [ ] Nettoyage des volumes Docker
|
||||
- [ ] Vérification de la connectivité réseau
|
||||
- [ ] Mise à jour des dépendances
|
||||
- [ ] Vérification de la configuration
|
||||
|
||||
## 📚 Contexte Supplémentaire
|
||||
|
||||
Toute autre information pertinente sur le problème.
|
||||
|
||||
## 🔗 Liens Utiles
|
||||
|
||||
- [Documentation](docs/)
|
||||
- [Guide de Dépannage](docs/TROUBLESHOOTING.md)
|
||||
- [Issues Similaires](https://git.4nkweb.com/4nk/4NK_node/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
|
||||
---
|
||||
|
||||
**Merci de votre contribution !** 🙏
|
||||
|
||||
@ -1,159 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Proposer une nouvelle fonctionnalité pour docv
|
||||
title: '[FEATURE] '
|
||||
labels: ['enhancement', 'needs-triage']
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
> Ce fichier est un modèle (template). Adaptez les champs à votre projet dérivé.
|
||||
|
||||
## 🚀 Résumé
|
||||
|
||||
Description claire et concise de la fonctionnalité souhaitée.
|
||||
|
||||
## 💡 Motivation
|
||||
|
||||
Pourquoi cette fonctionnalité est-elle nécessaire ? Quels problèmes résout-elle ?
|
||||
|
||||
### Problèmes Actuels
|
||||
- Problème 1
|
||||
- Problème 2
|
||||
- Problème 3
|
||||
|
||||
### Avantages de la Solution
|
||||
- Avantage 1
|
||||
- Avantage 2
|
||||
- Avantage 3
|
||||
|
||||
## 🎯 Proposition
|
||||
|
||||
Description détaillée de la fonctionnalité proposée.
|
||||
|
||||
### Fonctionnalités Principales
|
||||
- [ ] Fonctionnalité 1
|
||||
- [ ] Fonctionnalité 2
|
||||
- [ ] Fonctionnalité 3
|
||||
|
||||
### Interface Utilisateur
|
||||
Description de l'interface utilisateur si applicable.
|
||||
|
||||
### API Changes
|
||||
Description des changements d'API si applicable.
|
||||
|
||||
## 🔄 Alternatives Considérées
|
||||
|
||||
Autres solutions envisagées et pourquoi elles n'ont pas été choisies.
|
||||
|
||||
### Alternative 1
|
||||
- **Description** : ...
|
||||
- **Pourquoi rejetée** : ...
|
||||
|
||||
### Alternative 2
|
||||
- **Description** : ...
|
||||
- **Pourquoi rejetée** : ...
|
||||
|
||||
## 📊 Impact
|
||||
|
||||
### Impact sur les Utilisateurs
|
||||
- Impact positif 1
|
||||
- Impact positif 2
|
||||
- Impact négatif potentiel (si applicable)
|
||||
|
||||
### Impact sur l'Architecture
|
||||
- Changements nécessaires
|
||||
- Compatibilité avec l'existant
|
||||
- Performance
|
||||
|
||||
### Impact sur la Maintenance
|
||||
- Complexité ajoutée
|
||||
- Tests nécessaires
|
||||
- Documentation requise
|
||||
|
||||
## 💻 Exemples d'Utilisation
|
||||
|
||||
### Cas d'Usage 1
|
||||
```bash
|
||||
# Exemple de commande ou configuration
|
||||
```
|
||||
|
||||
### Cas d'Usage 2
|
||||
```python
|
||||
# Exemple de code Python
|
||||
```
|
||||
|
||||
### Cas d'Usage 3
|
||||
```javascript
|
||||
// Exemple de code JavaScript
|
||||
```
|
||||
|
||||
## 🧪 Tests
|
||||
|
||||
### Tests Nécessaires
|
||||
- [ ] Tests unitaires
|
||||
- [ ] Tests d'intégration
|
||||
- [ ] Tests de performance
|
||||
- [ ] Tests de sécurité
|
||||
- [ ] Tests de compatibilité
|
||||
|
||||
### Scénarios de Test
|
||||
- Scénario 1
|
||||
- Scénario 2
|
||||
- Scénario 3
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### Documentation Requise
|
||||
- [ ] Guide d'utilisation
|
||||
- [ ] Documentation API
|
||||
- [ ] Exemples de code
|
||||
- [ ] Guide de migration
|
||||
- [ ] FAQ
|
||||
|
||||
## 🔧 Implémentation
|
||||
|
||||
### Étapes Proposées
|
||||
1. **Phase 1** : [Description]
|
||||
2. **Phase 2** : [Description]
|
||||
3. **Phase 3** : [Description]
|
||||
|
||||
### Estimation de Temps
|
||||
- **Développement** : X jours/semaines
|
||||
- **Tests** : X jours/semaines
|
||||
- **Documentation** : X jours/semaines
|
||||
- **Total** : X jours/semaines
|
||||
|
||||
### Ressources Nécessaires
|
||||
- Développeur(s)
|
||||
- Testeur(s)
|
||||
- Documentateur(s)
|
||||
- Infrastructure
|
||||
|
||||
## 🎯 Critères de Succès
|
||||
|
||||
Comment mesurer le succès de cette fonctionnalité ?
|
||||
|
||||
- [ ] Critère 1
|
||||
- [ ] Critère 2
|
||||
- [ ] Critère 3
|
||||
|
||||
## 🔗 Liens Utiles
|
||||
|
||||
- [Documentation existante](docs/)
|
||||
- [Issues similaires](https://git.4nkweb.com/4nk/4NK_node/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
|
||||
- [Roadmap](https://git.4nkweb.com/4nk/4NK_node/projects)
|
||||
- [Discussions](https://git.4nkweb.com/4nk/4NK_node/issues)
|
||||
|
||||
## 📋 Checklist
|
||||
|
||||
- [ ] J'ai vérifié que cette fonctionnalité n'existe pas déjà
|
||||
- [ ] J'ai lu la documentation existante
|
||||
- [ ] J'ai vérifié les issues similaires
|
||||
- [ ] J'ai fourni des exemples d'utilisation
|
||||
- [ ] J'ai considéré l'impact sur l'existant
|
||||
- [ ] J'ai proposé des tests
|
||||
|
||||
---
|
||||
|
||||
**Merci de votre contribution à l'amélioration de docv !** 🌟
|
||||
|
||||
@ -1,184 +0,0 @@
|
||||
# Pull Request - docv
|
||||
|
||||
> Ce fichier est un modèle PR. Adaptez les sections à votre projet dérivé.
|
||||
|
||||
## 📋 Description
|
||||
|
||||
Description claire et concise des changements apportés.
|
||||
|
||||
### Type de Changement
|
||||
- [ ] 🐛 Bug fix
|
||||
- [ ] ✨ Nouvelle fonctionnalité
|
||||
- [ ] 📚 Documentation
|
||||
- [ ] 🧪 Tests
|
||||
- [ ] 🔧 Refactoring
|
||||
- [ ] 🚀 Performance
|
||||
- [ ] 🔒 Sécurité
|
||||
- [ ] 🎨 Style/UI
|
||||
- [ ] 🏗️ Architecture
|
||||
- [ ] 📦 Build/CI
|
||||
|
||||
### Composants Affectés
|
||||
- [ ] Bitcoin Core
|
||||
- [ ] Blindbit
|
||||
- [ ] SDK Relay
|
||||
- [ ] Tor
|
||||
- [ ] Docker/Infrastructure
|
||||
- [ ] Tests
|
||||
- [ ] Documentation
|
||||
- [ ] Scripts
|
||||
|
||||
## 🔗 Issue(s) Liée(s)
|
||||
|
||||
Fixes #(issue)
|
||||
Relates to #(issue)
|
||||
|
||||
## 🧪 Tests
|
||||
|
||||
### Tests Exécutés
|
||||
- [ ] Tests unitaires
|
||||
- [ ] Tests d'intégration
|
||||
- [ ] Tests de connectivité
|
||||
- [ ] Tests externes
|
||||
- [ ] Tests de performance
|
||||
- [ ] Release Guard local (`RELEASE_TYPE=ci-verify scripts/release/guard.sh`)
|
||||
|
||||
### Commandes de Test
|
||||
```bash
|
||||
# Tests complets
|
||||
./tests/run_all_tests.sh
|
||||
|
||||
# Tests spécifiques
|
||||
./tests/run_unit_tests.sh
|
||||
./tests/run_integration_tests.sh
|
||||
```
|
||||
|
||||
### Résultats des Tests
|
||||
```
|
||||
Résultats des tests ici
|
||||
```
|
||||
|
||||
## 📸 Captures d'Écran
|
||||
|
||||
Si applicable, ajoutez des captures d'écran pour les changements visuels.
|
||||
|
||||
## 🔧 Changements Techniques
|
||||
|
||||
### Fichiers Modifiés
|
||||
- `fichier1.rs` - Description des changements
|
||||
- `fichier2.py` - Description des changements
|
||||
- `docker-compose.yml` - Description des changements
|
||||
|
||||
### Nouveaux Fichiers
|
||||
- `nouveau_fichier.rs` - Description
|
||||
- `nouveau_script.sh` - Description
|
||||
|
||||
### Fichiers Supprimés
|
||||
- `ancien_fichier.rs` - Raison de la suppression
|
||||
|
||||
### Changements de Configuration
|
||||
```yaml
|
||||
# Exemple de changement de configuration
|
||||
service:
|
||||
new_option: value
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### Documentation Mise à Jour
|
||||
- [ ] README.md
|
||||
- [ ] docs/INSTALLATION.md
|
||||
- [ ] docs/USAGE.md
|
||||
- [ ] docs/API.md
|
||||
- [ ] docs/ARCHITECTURE.md
|
||||
|
||||
### Nouvelle Documentation
|
||||
- [ ] Nouveau guide créé
|
||||
- [ ] Exemples ajoutés
|
||||
- [ ] API documentée
|
||||
|
||||
## 🔍 Code Review Checklist
|
||||
|
||||
### Code Quality
|
||||
- [ ] Le code suit les standards du projet
|
||||
- [ ] Les noms de variables/fonctions sont clairs
|
||||
- [ ] Les commentaires sont appropriés
|
||||
- [ ] Pas de code mort ou commenté
|
||||
- [ ] Gestion d'erreurs appropriée
|
||||
|
||||
### Performance
|
||||
- [ ] Pas de régression de performance
|
||||
- [ ] Optimisations appliquées si nécessaire
|
||||
- [ ] Tests de performance ajoutés
|
||||
|
||||
### Sécurité
|
||||
- [ ] Pas de vulnérabilités introduites
|
||||
- [ ] Validation des entrées utilisateur
|
||||
- [ ] Gestion sécurisée des secrets
|
||||
|
||||
### Tests
|
||||
- [ ] Couverture de tests suffisante
|
||||
- [ ] Tests pour les cas d'erreur
|
||||
- [ ] Tests d'intégration si nécessaire
|
||||
|
||||
### Documentation
|
||||
- [ ] Code auto-documenté
|
||||
- [ ] Documentation mise à jour
|
||||
- [ ] Exemples fournis
|
||||
|
||||
## 🚀 Déploiement
|
||||
|
||||
### Impact sur le Déploiement
|
||||
- [ ] Aucun impact
|
||||
- [ ] Migration de données requise
|
||||
- [ ] Changement de configuration
|
||||
- [ ] Redémarrage des services
|
||||
|
||||
### Étapes de Déploiement
|
||||
```bash
|
||||
# Étapes pour déployer les changements
|
||||
```
|
||||
|
||||
## 📊 Métriques
|
||||
|
||||
### Impact sur les Performances
|
||||
- Temps de réponse : +/- X%
|
||||
- Utilisation mémoire : +/- X%
|
||||
- Utilisation CPU : +/- X%
|
||||
|
||||
### Impact sur la Stabilité
|
||||
- Taux d'erreur : +/- X%
|
||||
- Disponibilité : +/- X%
|
||||
|
||||
## 🔄 Compatibilité
|
||||
|
||||
### Compatibilité Ascendante
|
||||
- [ ] Compatible avec les versions précédentes
|
||||
- [ ] Migration automatique
|
||||
- [ ] Migration manuelle requise
|
||||
|
||||
### Compatibilité Descendante
|
||||
- [ ] Compatible avec les futures versions
|
||||
- [ ] API stable
|
||||
- [ ] Configuration stable
|
||||
|
||||
## 🎯 Critères de Succès
|
||||
|
||||
- [ ] Critère 1
|
||||
- [ ] Critère 2
|
||||
- [ ] Critère 3
|
||||
|
||||
## 📝 Notes Supplémentaires
|
||||
|
||||
Informations supplémentaires importantes pour les reviewers.
|
||||
|
||||
## 🔗 Liens Utiles
|
||||
|
||||
- [Documentation](docs/)
|
||||
- [Tests](tests/)
|
||||
- [Issues liées](https://git.4nkweb.com/4nk/4NK_node/issues)
|
||||
|
||||
---
|
||||
|
||||
**Merci pour votre contribution !** 🙏
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
# LOCAL_OVERRIDES.yml — dérogations locales contrôlées (fichier modèle)
|
||||
overrides:
|
||||
- path: ".gitea/workflows/ci.yml"
|
||||
reason: "spécificité d’environnement"
|
||||
owner: "@maintainer_handle"
|
||||
expires: "2025-12-31"
|
||||
- path: "scripts/scripts/auto-ssh-push.sh"
|
||||
reason: "flux particulier temporaire"
|
||||
owner: "@maintainer_handle"
|
||||
expires: "2025-10-01"
|
||||
policy:
|
||||
allow_only_listed_paths: true
|
||||
require_expiry: true
|
||||
audit_in_ci: true
|
||||
|
||||
@ -1,371 +0,0 @@
|
||||
# Template CI - docv (ce fichier est un modèle, adaptez selon votre projet)
|
||||
name: CI - docv
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
|
||||
env:
|
||||
RUST_VERSION: '1.70'
|
||||
DOCKER_COMPOSE_VERSION: '2.20.0'
|
||||
|
||||
jobs:
|
||||
# Job de vérification du code
|
||||
code-quality:
|
||||
name: Code Quality
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
override: true
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
|
||||
- name: Run clippy
|
||||
run: |
|
||||
cd sdk_relay
|
||||
cargo clippy --all-targets --all-features -- -D warnings
|
||||
|
||||
- name: Run rustfmt
|
||||
run: |
|
||||
cd sdk_relay
|
||||
cargo fmt --all -- --check
|
||||
|
||||
- name: Check documentation
|
||||
run: |
|
||||
cd sdk_relay
|
||||
cargo doc --no-deps
|
||||
|
||||
- name: Check for TODO/FIXME
|
||||
run: |
|
||||
if grep -r "TODO\|FIXME" . --exclude-dir=.git --exclude-dir=target; then
|
||||
echo "Found TODO/FIXME comments. Please address them."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Job de tests unitaires
|
||||
unit-tests:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
override: true
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
cd sdk_relay
|
||||
cargo test --lib --bins
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
cd sdk_relay
|
||||
cargo test --tests
|
||||
|
||||
# Job de tests d'intégration
|
||||
integration-tests:
|
||||
name: Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
docker:
|
||||
image: docker:24.0.5
|
||||
options: >-
|
||||
--health-cmd "docker info"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 2375:2375
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build Docker images
|
||||
run: |
|
||||
docker build -t 4nk-node-bitcoin ./bitcoin
|
||||
docker build -t 4nk-node-blindbit ./blindbit
|
||||
docker build -t 4nk-node-sdk-relay -f ./sdk_relay/Dockerfile ..
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
# Tests de connectivité de base
|
||||
./tests/run_connectivity_tests.sh || true
|
||||
|
||||
# Tests d'intégration
|
||||
./tests/run_integration_tests.sh || true
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-results
|
||||
path: |
|
||||
tests/logs/
|
||||
tests/reports/
|
||||
retention-days: 7
|
||||
|
||||
# Job de tests de sécurité
|
||||
security-tests:
|
||||
name: Security Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
override: true
|
||||
|
||||
- name: Run cargo audit
|
||||
run: |
|
||||
cd sdk_relay
|
||||
cargo audit --deny warnings
|
||||
|
||||
- name: Check for secrets
|
||||
run: |
|
||||
# Vérifier les secrets potentiels
|
||||
if grep -r "password\|secret\|key\|token" . --exclude-dir=.git --exclude-dir=target --exclude=*.md; then
|
||||
echo "Potential secrets found. Please review."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check file permissions
|
||||
run: |
|
||||
# Vérifier les permissions sensibles
|
||||
find . -type f -perm /0111 -name "*.conf" -o -name "*.key" -o -name "*.pem" | while read file; do
|
||||
if [[ $(stat -c %a "$file") != "600" ]]; then
|
||||
echo "Warning: $file has insecure permissions"
|
||||
fi
|
||||
done
|
||||
|
||||
# Job de build et test Docker
|
||||
docker-build:
|
||||
name: Docker Build & Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
docker:
|
||||
image: docker:24.0.5
|
||||
options: >-
|
||||
--health-cmd "docker info"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 2375:2375
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and test Bitcoin Core
|
||||
run: |
|
||||
docker build -t 4nk-node-bitcoin:test ./bitcoin
|
||||
docker run --rm 4nk-node-bitcoin:test bitcoin-cli --version
|
||||
|
||||
- name: Build and test Blindbit
|
||||
run: |
|
||||
docker build -t 4nk-node-blindbit:test ./blindbit
|
||||
docker run --rm 4nk-node-blindbit:test --version || true
|
||||
|
||||
- name: Build and test SDK Relay
|
||||
run: |
|
||||
docker build -t 4nk-node-sdk-relay:test -f ./sdk_relay/Dockerfile ..
|
||||
docker run --rm 4nk-node-sdk-relay:test --version || true
|
||||
|
||||
- name: Test Docker Compose
|
||||
run: |
|
||||
docker-compose config
|
||||
docker-compose build --no-cache
|
||||
|
||||
# Job de tests de documentation
|
||||
documentation-tests:
|
||||
name: Documentation Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check markdown links
|
||||
run: |
|
||||
# Vérification basique des liens markdown
|
||||
find . -name "*.md" -exec grep -l "\[.*\](" {} \; | while read file; do
|
||||
echo "Checking links in $file"
|
||||
done
|
||||
|
||||
- name: Check documentation structure
|
||||
run: |
|
||||
# Vérifier la présence des fichiers de documentation essentiels
|
||||
required_files=(
|
||||
"README.md"
|
||||
"LICENSE"
|
||||
"CONTRIBUTING.md"
|
||||
"CHANGELOG.md"
|
||||
"CODE_OF_CONDUCT.md"
|
||||
"SECURITY.md"
|
||||
"docs/INDEX.md"
|
||||
"docs/INSTALLATION.md"
|
||||
"docs/USAGE.md"
|
||||
)
|
||||
|
||||
for file in "${required_files[@]}"; do
|
||||
if [[ ! -f "$file" ]]; then
|
||||
echo "Missing required documentation file: $file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Validate documentation
|
||||
run: |
|
||||
echo "Validation documentation générique (adaptée au projet)"
|
||||
|
||||
security-audit:
|
||||
name: Security Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Ensure scripts executable
|
||||
run: |
|
||||
chmod +x scripts/security/audit.sh || true
|
||||
- name: Run template security audit
|
||||
run: |
|
||||
if [ -f scripts/security/audit.sh ]; then
|
||||
./scripts/security/audit.sh
|
||||
else
|
||||
echo "No security audit script (ok)"
|
||||
fi
|
||||
|
||||
# Job de release guard (cohérence release)
|
||||
release-guard:
|
||||
name: Release Guard
|
||||
runs-on: ubuntu-latest
|
||||
needs: [code-quality, unit-tests, documentation-tests, security-audit]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Ensure guard scripts are executable
|
||||
run: |
|
||||
chmod +x scripts/release/guard.sh || true
|
||||
chmod +x scripts/checks/version_alignment.sh || true
|
||||
|
||||
- name: Version alignment check
|
||||
run: |
|
||||
if [ -f scripts/checks/version_alignment.sh ]; then
|
||||
./scripts/checks/version_alignment.sh
|
||||
else
|
||||
echo "No version alignment script (ok)"
|
||||
fi
|
||||
|
||||
docker-build:
|
||||
name: Docker Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
docker build -t docv:latest .
|
||||
|
||||
- name: Release guard (CI verify)
|
||||
env:
|
||||
RELEASE_TYPE: ci-verify
|
||||
run: |
|
||||
if [ -f scripts/release/guard.sh ]; then
|
||||
./scripts/release/guard.sh
|
||||
else
|
||||
echo "No guard script (ok)"
|
||||
fi
|
||||
|
||||
# Job de tests de performance
|
||||
performance-tests:
|
||||
name: Performance Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
override: true
|
||||
|
||||
- name: Run performance tests
|
||||
run: |
|
||||
cd sdk_relay
|
||||
cargo test --release --test performance_tests || true
|
||||
|
||||
- name: Check memory usage
|
||||
run: |
|
||||
# Tests de base de consommation mémoire
|
||||
echo "Performance tests completed"
|
||||
|
||||
# Job de notification
|
||||
notify:
|
||||
name: Notify
|
||||
runs-on: ubuntu-latest
|
||||
needs: [code-quality, unit-tests, integration-tests, security-tests, docker-build, documentation-tests]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Notify success
|
||||
if: needs.code-quality.result == 'success' && needs.unit-tests.result == 'success' && needs.integration-tests.result == 'success' && needs.security-tests.result == 'success' && needs.docker-build.result == 'success' && needs.documentation-tests.result == 'success'
|
||||
run: |
|
||||
echo "✅ All tests passed successfully!"
|
||||
|
||||
- name: Notify failure
|
||||
if: needs.code-quality.result == 'failure' || needs.unit-tests.result == 'failure' || needs.integration-tests.result == 'failure' || needs.security-tests.result == 'failure' || needs.docker-build.result == 'failure' || needs.documentation-tests.result == 'failure'
|
||||
run: |
|
||||
echo "❌ Some tests failed!"
|
||||
exit 1
|
||||
@ -1,40 +0,0 @@
|
||||
# .gitea/workflows/template-sync.yml — synchronisation et contrôles d’intégrité (fichier modèle)
|
||||
name: 4NK Template Sync
|
||||
on:
|
||||
schedule: # planification régulière
|
||||
- cron: "0 4 * * 1" # exécution hebdomadaire (UTC)
|
||||
workflow_dispatch: {} # déclenchement manuel
|
||||
|
||||
jobs:
|
||||
check-and-sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Lire TEMPLATE_VERSION et .4nk-sync.yml
|
||||
# Doit charger ref courant, source_repo et périmètre paths
|
||||
|
||||
- name: Récupérer la version publiée du template/4NK_rules
|
||||
# Doit comparer TEMPLATE_VERSION avec ref amont
|
||||
|
||||
- name: Créer branche de synchronisation si divergence
|
||||
# Doit créer chore/template-sync-<date> et préparer un commit
|
||||
|
||||
- name: Synchroniser les chemins autoritatifs
|
||||
# Doit mettre à jour .cursor/**, .gitea/**, AGENTS.md, scripts/**, docs/SSH_UPDATE.md
|
||||
|
||||
- name: Contrôles post-sync (bloquants)
|
||||
# 1) Vérifier présence et exécutable des scripts/*.sh
|
||||
# 2) Vérifier mise à jour CHANGELOG.md et docs/INDEX.md
|
||||
# 3) Vérifier docs/SSH_UPDATE.md si scripts/** a changé
|
||||
# 4) Vérifier absence de secrets en clair dans scripts/**
|
||||
# 5) Vérifier manifest_checksum si publié
|
||||
|
||||
- name: Tests, lint, sécurité statique
|
||||
# Doit exiger un état vert
|
||||
|
||||
- name: Ouvrir PR de synchronisation
|
||||
# Titre: "[template-sync] chore: aligner .cursor/.gitea/AGENTS.md/scripts"
|
||||
# Doit inclure résumé des fichiers modifiés et la version appliquée
|
||||
|
||||
- name: Mettre à jour TEMPLATE_VERSION (dans PR)
|
||||
# Doit remplacer la valeur par la ref appliquée
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@ -20,12 +20,6 @@ yarn-error.log*
|
||||
# env files
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
!.cursor/
|
||||
|
||||
!AGENTS.md
|
||||
next-env.d.ts
|
||||
275
AGENTS.md
275
AGENTS.md
@ -1,275 +0,0 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Table des matières
|
||||
- [Introduction](#introduction)
|
||||
- [Principes communs](#principes-communs)
|
||||
- [Agents fondamentaux](#agents-fondamentaux)
|
||||
- [Agents spécialisés documentation](#agents-spécialisés-documentation)
|
||||
- [Agents spécialisés tests](#agents-spécialisés-tests)
|
||||
- [Agents techniques](#agents-techniques)
|
||||
- [Agents frontend](#agents-frontend)
|
||||
- [Agents open source et CI](#agents-open-source-et-ci)
|
||||
- [Agents de synchronisation et dérogations](#agents-de-synchronisation-et-d%C3%A9rogations)
|
||||
- [Matrice de coordination](#matrice-de-coordination)
|
||||
- [Conclusion](#conclusion)
|
||||
|
||||
---
|
||||
|
||||
## Introduction
|
||||
Ce document définit les agents, leurs rôles et leurs responsabilités dans le projet `4NK/4NK_node` et, par extension, tout dépôt dérivé de `4NK_project_template`.
|
||||
Il impose une coordination stricte entre code, documentation, tests, dépendances, CI/CD, synchronisation de template et gouvernance open source.
|
||||
Les règles opérationnelles détaillées sont précisées dans `.cursor/rules/` (notamment `41-ssh-automation.mdc` et `42-template-sync.mdc`).
|
||||
|
||||
---
|
||||
|
||||
## Principes communs
|
||||
- Langue exclusive : français.
|
||||
- Pas d’exemples de code applicatif injectés dans la base.
|
||||
- Toute contribution doit contenir une introduction et/ou une conclusion.
|
||||
- Interdiction de secrets en clair dans le dépôt.
|
||||
- Confirmation nécessaire avant `push` et `tag`.
|
||||
- Toute modification impactant des éléments normatifs doit mettre à jour la documentation et le changelog.
|
||||
- Le flux de publication applique un garde de release (tests/doc/build/alignement version/changelog/tag, latest vs wip).
|
||||
|
||||
---
|
||||
|
||||
## Agents fondamentaux
|
||||
|
||||
### Agent Fondation (Responsable)
|
||||
**Missions**
|
||||
- Garantir la conformité éditoriale : français, pas d’exemples applicatifs, introduction/conclusion.
|
||||
- Vérifier la cohérence terminologique.
|
||||
|
||||
**Artefacts**
|
||||
- Tous fichiers.
|
||||
|
||||
---
|
||||
|
||||
### Agent Structure (Responsable)
|
||||
**Missions**
|
||||
- Maintenir l’arborescence canonique (incluant `.cursor/`, `.gitea/`, `scripts/`, `docs/SSH_UPDATE.md`).
|
||||
- Archiver le contenu obsolète dans `archive/` avec métadonnées.
|
||||
- Interdire toute suppression non tracée.
|
||||
|
||||
**Artefacts**
|
||||
- `archive/`, `docs/**`, `tests/**`, `.cursor/**`, `.gitea/**`, `scripts/**`, `CHANGELOG.md`.
|
||||
|
||||
---
|
||||
|
||||
## Agents spécialisés documentation
|
||||
|
||||
### Agent Documentation (Responsable)
|
||||
**Missions**
|
||||
- Mettre à jour `docs/**` selon l’impact des changements.
|
||||
- Tenir `docs/INDEX.md` comme table des matières centrale.
|
||||
- Produire des REX techniques dans `archive/` en cas d’investigations multiples.
|
||||
|
||||
**Artefacts**
|
||||
- `docs/**`, `README.md`, `archive/**`.
|
||||
|
||||
---
|
||||
|
||||
### Agent Données CSV (Responsable)
|
||||
**Missions**
|
||||
- Traiter les CSV comme source des modèles de données (en-têtes multi-lignes inclus).
|
||||
- Exiger une définition complète de toutes les colonnes.
|
||||
- Corriger et documenter les incohérences de types.
|
||||
|
||||
**Artefacts**
|
||||
- `docs/API.md`, `docs/ARCHITECTURE.md`, `docs/USAGE.md`.
|
||||
|
||||
---
|
||||
|
||||
### Agent Documents bureautiques (Consulté)
|
||||
**Missions**
|
||||
- Lire `.docx` via `docx2txt`; proposer des alternatives en cas d’échec.
|
||||
- Documenter les imports dans `docs/INDEX.md`.
|
||||
|
||||
**Artefacts**
|
||||
- `docs/**`, `archive/**`.
|
||||
|
||||
---
|
||||
|
||||
## Agents spécialisés tests
|
||||
|
||||
### Agent Tests (Responsable)
|
||||
**Missions**
|
||||
- Couvrir `unit`, `integration`, `connectivity`, `performance`, `external`.
|
||||
- Gérer `tests/logs` et `tests/reports`.
|
||||
- Exiger des tests verts avant commit.
|
||||
|
||||
**Artefacts**
|
||||
- `tests/**`, `docs/TESTING.md`.
|
||||
|
||||
---
|
||||
|
||||
### Agent Performance (Consulté)
|
||||
**Missions**
|
||||
- Réaliser des benchmarks reproductibles.
|
||||
- Valider l’impact performance avant fusion.
|
||||
|
||||
**Artefacts**
|
||||
- `tests/performance/`, `tests/reports/`, `docs/TESTING.md`.
|
||||
|
||||
---
|
||||
|
||||
## Agents techniques
|
||||
|
||||
### Agent Dépendances (Responsable)
|
||||
**Missions**
|
||||
- Ajouter les dépendances manquantes lorsque justifié.
|
||||
- Vérifier les dernières versions stables.
|
||||
- Documenter les impacts dans `ARCHITECTURE.md`, `CONFIGURATION.md`, `CHANGELOG.md`.
|
||||
|
||||
**Artefacts**
|
||||
- `docs/ARCHITECTURE.md`, `docs/CONFIGURATION.md`, `CHANGELOG.md`.
|
||||
|
||||
---
|
||||
|
||||
### Agent Compilation (Responsable)
|
||||
**Missions**
|
||||
- Compiler très régulièrement et aux étapes critiques.
|
||||
- Bloquer toute progression en cas d’erreurs de build/runtime.
|
||||
|
||||
**Artefacts**
|
||||
- Artefacts de build, scripts d’outillage.
|
||||
|
||||
---
|
||||
|
||||
### Agent Résolution (Responsable)
|
||||
**Missions**
|
||||
- Conduire la boucle de diagnostic complète : reproduction minimale, logs, bissection, hypothèses, tests ciblés, correctif, non-régression.
|
||||
- Produire un REX quand plusieurs hypothèses ont été testées.
|
||||
|
||||
**Artefacts**
|
||||
- `tests/**`, `archive/**`
|
||||
|
||||
---
|
||||
|
||||
### Agent SSH & scripts (Responsable)
|
||||
**Missions**
|
||||
- Garantir la présence et l’usage correct de `scripts/auto-ssh-push.sh`, `scripts/init-ssh-env.sh`, `scripts/setup-ssh-ci.sh`.
|
||||
- Assurer permissions d’exécution, idempotence, journalisation non sensible, gestion d’erreurs robuste.
|
||||
- Interdire secrets en clair, gérer via secrets CI et variables d’environnement.
|
||||
- Exiger la mise à jour de `docs/SSH_UPDATE.md` à toute évolution des scripts ou des flux SSH.
|
||||
|
||||
**Artefacts**
|
||||
- `scripts/**`, `.gitea/workflows/ci.yml`, `docs/SSH_UPDATE.md`, `docs/CONFIGURATION.md`, `CHANGELOG.md`.
|
||||
|
||||
---
|
||||
|
||||
## Agents frontend
|
||||
|
||||
### Agent Frontend (Responsable)
|
||||
**Missions**
|
||||
- Mettre en place `React.lazy`/`Suspense` (code splitting).
|
||||
- Centraliser l’état (Redux ou Context API).
|
||||
- Abstraire les services de données.
|
||||
|
||||
**Artefacts**
|
||||
- `docs/ARCHITECTURE.md`, `docs/TESTING.md`.
|
||||
|
||||
---
|
||||
|
||||
## Agents open source et CI
|
||||
|
||||
### Agent Open Source (Responsable)
|
||||
**Missions**
|
||||
- Maintenir : `LICENSE`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `docs/OPEN_SOURCE_CHECKLIST.md`.
|
||||
- Vérifier l’alignement continu avec `4NK_node`.
|
||||
|
||||
**Artefacts**
|
||||
- Fichiers de gouvernance cités ci-dessus.
|
||||
|
||||
---
|
||||
|
||||
### Agent Gitea (Responsable)
|
||||
**Missions**
|
||||
- Garantir `.gitea/ISSUE_TEMPLATE/*`, `PULL_REQUEST_TEMPLATE.md`, `.gitea/workflows/ci.yml`.
|
||||
- Documenter la configuration distante dans `docs/GITEA_SETUP.md`.
|
||||
- Déclencher les étapes CI pertinentes (tests, lint, sécurité, vérifs `scripts/`).
|
||||
|
||||
**Artefacts**
|
||||
- `.gitea/**`, `docs/GITEA_SETUP.md`.
|
||||
|
||||
---
|
||||
|
||||
### Agent Versionnage (Responsable)
|
||||
**Missions**
|
||||
- Tenir `CHANGELOG.md` comme source unique de vérité.
|
||||
- Proposer un bump sémantique justifié.
|
||||
- Demander confirmation avant push et tag.
|
||||
- Orchestrer le `release-guard` (CI + scripts) et consigner latest vs wip.
|
||||
|
||||
**Artefacts**
|
||||
- `CHANGELOG.md`, `docs/RELEASE_PLAN.md`, `docs/ROADMAP.md`.
|
||||
|
||||
---
|
||||
|
||||
### Agent Sécurité (Responsable)
|
||||
**Missions**
|
||||
- Mettre en place et exécuter `scripts/security/audit.sh` (npm audit, cargo audit si applicable, scan de secrets).
|
||||
- Interdire les secrets en clair; assurer la rotation des secrets CI.
|
||||
- Vérifier permissions et non‑exposition d’endpoints privés.
|
||||
- Bloquer la release si l’audit échoue (intégré au `release-guard`).
|
||||
|
||||
**Artefacts**
|
||||
- `scripts/security/audit.sh`, `.gitea/workflows/ci.yml` (job `security-audit`), `docs/SECURITY_AUDIT.md`, `SECURITY.md`.
|
||||
|
||||
---
|
||||
|
||||
## Agents de synchronisation et dérogations
|
||||
|
||||
### Agent Synchronisation de template (Accountable)
|
||||
**Références**
|
||||
- `.4nk-sync.yml` (manifeste), `TEMPLATE_VERSION` (pointeur), `.gitea/workflows/template-sync.yml` (CI dédiée).
|
||||
- Règles Cursor : `.cursor/rules/42-template-sync.mdc` et `.cursor/rules/10-project-structure.mdc`.
|
||||
|
||||
**Missions**
|
||||
- Assurer l’alignement automatique sur le template pour : `.cursor/**`, `.gitea/**`, `AGENTS.md`, `scripts/**`, `docs/SSH_UPDATE.md`.
|
||||
- Déclencher/valider la PR `[template-sync]` créée par la CI.
|
||||
- Exiger la mise à jour de `CHANGELOG.md` et `docs/INDEX.md` après synchronisation.
|
||||
- Vérifier l’intégrité (`manifest_checksum`, checksums de fichiers si publiés), les permissions, l’absence de secrets.
|
||||
- Mettre à jour `TEMPLATE_VERSION` dans la PR.
|
||||
|
||||
**Artefacts**
|
||||
- `.4nk-sync.yml`, `TEMPLATE_VERSION`, `.cursor/**`, `.gitea/**`, `AGENTS.md`, `scripts/**`, `docs/SSH_UPDATE.md`, `CHANGELOG.md`.
|
||||
|
||||
---
|
||||
|
||||
### Agent Dérogations locales (Responsable)
|
||||
**Références**
|
||||
- `LOCAL_OVERRIDES.yml` (facultatif, mais recommandé).
|
||||
|
||||
**Missions**
|
||||
- Enregistrer toute divergence locale dans le périmètre synchronisé (path, raison, propriétaire, échéance).
|
||||
- Faire respecter : seules les dérogations listées et non expirées sont tolérées par la CI.
|
||||
- Auditer périodiquement et résorber les dérogations.
|
||||
|
||||
**Artefacts**
|
||||
- `LOCAL_OVERRIDES.yml`, `CHANGELOG.md` (mentionner les dérogations significatives).
|
||||
|
||||
---
|
||||
|
||||
## Matrice de coordination
|
||||
|
||||
| Type de changement | Agents impliqués | Artefacts principaux | Validation obligatoire |
|
||||
|----------------------------------|----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|------------------------|
|
||||
| Ajout de fonctionnalité | Documentation, Tests, Dépendances, Frontend | `docs/API.md`, `docs/USAGE.md`, `docs/ARCHITECTURE.md`, `tests/unit`, `tests/integration`, `CHANGELOG.md` (*Added*) | Oui |
|
||||
| Correction de bug | Résolution, Tests, Documentation | `tests/unit`, `docs/TESTING.md`, `archive/` (REX si nécessaire), `CHANGELOG.md` (*Fixed*) | Oui |
|
||||
| Refactorisation / amélioration | Structure, Documentation, Compilation | `docs/ARCHITECTURE.md`, `archive/`, `CHANGELOG.md` (*Changed*) | Oui |
|
||||
| Dépendance ajoutée/mise à jour | Dépendances, Compilation, Documentation | `docs/ARCHITECTURE.md`, `docs/CONFIGURATION.md`, `CHANGELOG.md` (*Dependencies*) | Oui |
|
||||
| Données CSV modifiées | Données CSV, Documentation, Tests | `docs/API.md`, `docs/ARCHITECTURE.md`, `docs/USAGE.md`, `tests/unit`, `CHANGELOG.md` (*Data model update*) | Oui |
|
||||
| Migration / breaking change | Documentation, Tests, Résolution, Versionnage | `docs/MIGRATION.md`, `docs/INSTALLATION.md`, `docs/RELEASE_PLAN.md`, `docs/ROADMAP.md`, `tests/integration`, `CHANGELOG.md` (*Breaking*) | Oui |
|
||||
| Sécurité / audit | Documentation, Tests, Open Source, Sécurité proactive | `docs/SECURITY_AUDIT.md`, `tests/external`, `tests/connectivity`, `CHANGELOG.md` (*Security*) | Oui |
|
||||
| Préparation open source / CI | Open Source, Gitea, Versionnage, Documentation communautaire, Contributeurs externes | `.gitea/**`, `docs/GITEA_SETUP.md`, `docs/OPEN_SOURCE_CHECKLIST.md`, `CHANGELOG.md` (*CI/CD* / *Governance*) | Oui |
|
||||
| Optimisation performance | Performance, Tests, Documentation | `tests/performance`, `tests/reports`, `docs/ARCHITECTURE.md`, `CHANGELOG.md` (*Performance*) | Oui |
|
||||
| Évolution frontend | Frontend, Documentation, Tests | `docs/ARCHITECTURE.md`, `docs/USAGE.md`, `tests/integration`, `CHANGELOG.md` (*Frontend*) | Oui |
|
||||
| Évolution CI/CD ou scripts SSH | SSH & scripts, Gitea, Versionnage, Documentation | `scripts/**`, `.gitea/workflows/ci.yml`, `docs/SSH_UPDATE.md`, `docs/CONFIGURATION.md`, `CHANGELOG.md` (*CI/CD*) | Oui |
|
||||
| **Synchronisation de template** | **Synchronisation de template**, Gitea, Versionnage, Structure, Documentation, SSH & scripts | `.4nk-sync.yml`, `TEMPLATE_VERSION`, `.cursor/**`, `.gitea/**`, `AGENTS.md`, `scripts/**`, `docs/SSH_UPDATE.md`, `CHANGELOG.md` | **Oui** |
|
||||
| Dérogation locale contrôlée | Dérogations locales, Gitea, Synchronisation de template, Versionnage | `LOCAL_OVERRIDES.yml`, `CHANGELOG.md` (mention), CI tolérante uniquement sur chemins listés et non expirés | Oui |
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
Ce `AGENTS.md` mis à jour introduit l’**Agent Synchronisation de template** et l’**Agent Dérogations locales**, renforce l’**Agent SSH & scripts**, et rattache l’ensemble aux règles Cursor et à la CI Gitea. La matrice de coordination formalise les validations obligatoires pour chaque type de changement, garantissant cohérence structurelle, qualité documentaire, sécurité, traçabilité et stabilité à long terme sur tous les projets issus de `4NK_project_template`.
|
||||
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,8 +1,12 @@
|
||||
# Changelog - docv
|
||||
### 0.2.0 - 2025-09-04
|
||||
|
||||
## [Unreleased]
|
||||
- Invitations: remplacement du QR par passphrase (4 mots BIP‑39 FR + code 6) et envoi email optionnel (rôle, contexte, passphrase, lien).
|
||||
- Users: ajout du titre de document/dossier dans la modale d’invitation, notification avec récap (mots + code).
|
||||
- UI Chat: suppression des boutons Téléphone et Vidéo.
|
||||
- Libellés: « Changer de storage » → « Conservation », « Télécharger certificats » → « Certificats », « Configurer les rôles » → « Rôles ».
|
||||
- Navigation: remplacement « Documents/Dossiers » par onglets par type (`/dashboard/folders?type=...`).
|
||||
- Paramètres: icône rétablie dans la barre haute (accès `/dashboard/settings`).
|
||||
- Indicateur clé privée: icône clé qui flashe en rouge 0,4 s à l’événement `private-key-access`.
|
||||
- Correctifs: alignement des colonnes « Accès » et « Statut » dans la liste des dossiers; stabilisation des hooks du layout pour corriger l’erreur React #310.
|
||||
|
||||
|
||||
## [0.1.0] - 2025-08-27
|
||||
### Changed
|
||||
- Release latest (sécurité/CI/docs).
|
||||
|
||||
194
DEPLOYMENT.md
194
DEPLOYMENT.md
@ -1,194 +0,0 @@
|
||||
# 🚀 Guide de déploiement DocV avec envoi d'emails
|
||||
|
||||
## 📋 Prérequis
|
||||
|
||||
### 1. Variables d'environnement
|
||||
Configurez ces variables sur votre plateforme de déploiement :
|
||||
|
||||
\`\`\`env
|
||||
SMTP_HOST=votre-serveur-smtp
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=votre-email@domaine.com
|
||||
SMTP_PASSWORD=votre-mot-de-passe-application
|
||||
SMTP_FROM=votre-email@domaine.com
|
||||
\`\`\`
|
||||
|
||||
⚠️ **Important** : Utilisez toujours des mots de passe d'application, jamais vos mots de passe principaux.
|
||||
|
||||
## 🌐 Déploiement sur Vercel
|
||||
|
||||
### 1. Installation Vercel CLI
|
||||
\`\`\`bash
|
||||
npm i -g vercel
|
||||
\`\`\`
|
||||
|
||||
### 2. Configuration des variables
|
||||
\`\`\`bash
|
||||
vercel env add SMTP_HOST
|
||||
vercel env add SMTP_PORT
|
||||
vercel env add SMTP_SECURE
|
||||
vercel env add SMTP_USER
|
||||
vercel env add SMTP_PASSWORD
|
||||
vercel env add SMTP_FROM
|
||||
\`\`\`
|
||||
|
||||
### 3. Déploiement
|
||||
\`\`\`bash
|
||||
vercel --prod
|
||||
\`\`\`
|
||||
|
||||
## 🔧 Déploiement sur Netlify
|
||||
|
||||
### 1. Variables d'environnement
|
||||
Dans le dashboard Netlify :
|
||||
- Site settings > Environment variables
|
||||
- Ajoutez toutes les variables SMTP
|
||||
|
||||
### 2. Build settings
|
||||
\`\`\`toml
|
||||
# netlify.toml
|
||||
[build]
|
||||
command = "npm run build"
|
||||
publish = ".next"
|
||||
|
||||
[build.environment]
|
||||
NODE_VERSION = "18"
|
||||
\`\`\`
|
||||
|
||||
## 🐳 Déploiement Docker
|
||||
|
||||
### 1. Dockerfile
|
||||
\`\`\`dockerfile
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
\`\`\`
|
||||
|
||||
### 2. Variables d'environnement
|
||||
\`\`\`bash
|
||||
docker run -d \
|
||||
-p 3000:3000 \
|
||||
-e SMTP_HOST=votre-smtp-host \
|
||||
-e SMTP_PORT=587 \
|
||||
-e SMTP_USER=votre-email@domaine.com \
|
||||
-e SMTP_PASSWORD=votre-mot-de-passe \
|
||||
-e SMTP_FROM=votre-email@domaine.com \
|
||||
docv-app
|
||||
\`\`\`
|
||||
|
||||
## ✅ Test de l'envoi d'emails
|
||||
|
||||
### 1. Vérification des variables
|
||||
\`\`\`bash
|
||||
# Sur votre serveur
|
||||
echo $SMTP_HOST
|
||||
echo $SMTP_USER
|
||||
\`\`\`
|
||||
|
||||
### 2. Test des formulaires
|
||||
- Accédez à \`/contact\`
|
||||
- Remplissez et envoyez le formulaire
|
||||
- Vérifiez les logs serveur
|
||||
- Vérifiez la réception dans votre boîte email
|
||||
|
||||
## 🔍 Debugging
|
||||
|
||||
### 1. Logs serveur
|
||||
\`\`\`bash
|
||||
# Vercel
|
||||
vercel logs
|
||||
|
||||
# Netlify
|
||||
netlify logs
|
||||
|
||||
# Docker
|
||||
docker logs container-name
|
||||
\`\`\`
|
||||
|
||||
### 2. Test SMTP manuel
|
||||
\`\`\`javascript
|
||||
// test-smtp.js
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: process.env.SMTP_PORT,
|
||||
secure: process.env.SMTP_SECURE === 'true',
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
},
|
||||
});
|
||||
|
||||
transporter.sendMail({
|
||||
from: process.env.SMTP_FROM,
|
||||
to: 'test@example.com',
|
||||
subject: 'Test SMTP',
|
||||
text: 'Test de configuration SMTP'
|
||||
}).then(() => {
|
||||
console.log('✅ SMTP fonctionne');
|
||||
}).catch(err => {
|
||||
console.error('❌ Erreur SMTP:', err);
|
||||
});
|
||||
\`\`\`
|
||||
|
||||
## 🔐 Sécurité
|
||||
|
||||
### 1. Variables d'environnement
|
||||
- ✅ Jamais dans le code source
|
||||
- ✅ Configurées sur la plateforme de déploiement
|
||||
- ✅ Différentes par environnement (dev/prod)
|
||||
|
||||
### 2. Mots de passe d'application
|
||||
- ✅ Utilisez des mots de passe d'application
|
||||
- ✅ Pas les mots de passe principaux des comptes
|
||||
- ✅ Révocables si compromis
|
||||
|
||||
## 📧 Configuration par fournisseur
|
||||
|
||||
### Protonmail
|
||||
\`\`\`env
|
||||
SMTP_HOST=smtp.protonmail.ch
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
\`\`\`
|
||||
|
||||
### Gmail
|
||||
\`\`\`env
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
\`\`\`
|
||||
|
||||
### Serveur SMTP dédié
|
||||
\`\`\`env
|
||||
SMTP_HOST=mail.votre-domaine.com
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
\`\`\`
|
||||
|
||||
## 🎯 Résultat attendu
|
||||
|
||||
Une fois déployé avec les bonnes variables :
|
||||
- ✅ Formulaires fonctionnels
|
||||
- ✅ Emails HTML formatés
|
||||
- ✅ Réception dans votre boîte email
|
||||
- ✅ Logs de confirmation
|
||||
- ✅ Gestion d'erreurs robuste
|
||||
|
||||
## 📞 Support
|
||||
|
||||
En cas de problème :
|
||||
1. Vérifiez les variables d'environnement
|
||||
2. Consultez les logs serveur
|
||||
3. Testez la configuration SMTP manuellement
|
||||
4. Vérifiez les paramètres de votre fournisseur email
|
||||
14
Dockerfile
14
Dockerfile
@ -1,14 +0,0 @@
|
||||
# Multi-stage build for Debian-based Node runtime
|
||||
FROM node:20-bookworm-slim AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci || npm install
|
||||
COPY . .
|
||||
RUN npm run build || npm run build:prod || true
|
||||
|
||||
FROM node:20-bookworm-slim
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=builder /app .
|
||||
EXPOSE 3000
|
||||
CMD ["npm","run","start","--if-present"]
|
||||
372
README.md
372
README.md
@ -1,372 +0,0 @@
|
||||
# 🛡️ DocV - GED Souveraine et Sécurisée
|
||||
|
||||
> **Une approche révolutionnaire de la gestion documentaire avec sécurité, souveraineté et conformité garanties.**
|
||||
|
||||
[](VERSION)
|
||||
[](https://nextjs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://tailwindcss.com/)
|
||||
[](#license)
|
||||
|
||||
## 📋 Table des Matières
|
||||
|
||||
- [🎯 Vue d'ensemble](#-vue-densemble)
|
||||
- [✨ Fonctionnalités](#-fonctionnalités)
|
||||
- [🚀 Installation Rapide](#-installation-rapide)
|
||||
- [⚙️ Configuration](#️-configuration)
|
||||
- [🔧 Commandes de Développement](#-commandes-de-développement)
|
||||
- [📚 Documentation](#-documentation)
|
||||
- [🏗️ Architecture](#️-architecture)
|
||||
- [🔒 Sécurité](#-sécurité)
|
||||
- [🤝 Contribution](#-contribution)
|
||||
- [📞 Support](#-support)
|
||||
|
||||
## 🎯 Vue d'ensemble
|
||||
|
||||
**DocV** est une plateforme de gestion documentaire (GED) révolutionnaire qui combine :
|
||||
|
||||
- **🔐 Authentification cryptographique** sans mots de passe
|
||||
- **🤖 IA embarquée** pour l'OCR et la classification
|
||||
- **🌐 Architecture souveraine** sans dépendance cloud
|
||||
- **⚡ Interface conversationnelle** pour le suivi des dossiers
|
||||
- **🔗 Ancrage blockchain** pour la traçabilité
|
||||
|
||||
### 🎯 Cas d'Usage Principaux
|
||||
|
||||
- **Entreprises** : Gestion documentaire sécurisée
|
||||
- **Notaires** : Échanges documentaires via lecoffre.io
|
||||
- **Secteur public** : Conformité et souveraineté des données
|
||||
- **Éditeurs** : Intégration marque blanche
|
||||
|
||||
## ✨ Fonctionnalités
|
||||
|
||||
### 🔑 Authentification Ultra-Simplifiée
|
||||
- ✅ Aucun mot de passe requis
|
||||
- ✅ Aucun OTP ou code SMS
|
||||
- ✅ Aucune application mobile
|
||||
- ✅ Identité auto-générée et auto-portée
|
||||
|
||||
### 🤖 Intelligence Artificielle Locale
|
||||
- ✅ OCR automatique des documents
|
||||
- ✅ Classification intelligente
|
||||
- ✅ Extraction de données
|
||||
- ✅ Interface conversationnelle
|
||||
- ✅ Traitements 100% locaux
|
||||
|
||||
### 🛡️ Sécurité de Bout en Bout
|
||||
- ✅ Chiffrement natif
|
||||
- ✅ Aucune interface admin exposée
|
||||
- ✅ Aucun serveur d'identité
|
||||
- ✅ Aucune dépendance cloud
|
||||
- ✅ Conformité RGPD, ISO 27001, SecNumCloud
|
||||
|
||||
### 🌐 Architecture Souveraine
|
||||
- ✅ Déploiement local
|
||||
- ✅ Migration automatisée
|
||||
- ✅ Compatible bases existantes
|
||||
- ✅ APIs souveraines
|
||||
- ✅ Accompagnement personnalisé
|
||||
|
||||
## 🚀 Installation Rapide
|
||||
|
||||
### 📋 Prérequis
|
||||
|
||||
- **Node.js** : Version 18.0+ (recommandé 20.x)
|
||||
- **npm** ou **pnpm** : Gestionnaire de paquets
|
||||
- **Git** : Pour le clonage du repository
|
||||
|
||||
### 1️⃣ Cloner le Repository
|
||||
|
||||
```bash
|
||||
# Cloner le projet
|
||||
git clone <REPO_URL>
|
||||
cd docv
|
||||
|
||||
# Installer les dépendances
|
||||
npm install
|
||||
# ou
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 2️⃣ Configuration d'Environnement
|
||||
|
||||
```bash
|
||||
# Créer le fichier d'environnement
|
||||
cp .env.example .env.local
|
||||
|
||||
# Éditer les variables d'environnement
|
||||
nano .env.local
|
||||
```
|
||||
|
||||
**Variables essentielles :**
|
||||
```env
|
||||
# Configuration de base
|
||||
NEXT_PUBLIC_APP_NAME=DocV
|
||||
NEXT_PUBLIC_APP_VERSION=0.1.0
|
||||
|
||||
# Base de données (si applicable)
|
||||
DATABASE_URL=your_database_url
|
||||
|
||||
# Authentification
|
||||
NEXTAUTH_SECRET=your_secret_key
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
|
||||
# Services externes (optionnels)
|
||||
EMAIL_SERVICE_API_KEY=your_email_api_key
|
||||
```
|
||||
|
||||
### 3️⃣ Démarrage en Mode Développement
|
||||
|
||||
```bash
|
||||
# Démarrer le serveur de développement
|
||||
npm run dev
|
||||
# ou
|
||||
pnpm dev
|
||||
|
||||
# L'application sera disponible sur http://localhost:3000
|
||||
```
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### 🎨 Configuration de l'Interface
|
||||
|
||||
Le projet utilise **Tailwind CSS** avec des composants **Radix UI** pour une interface moderne et accessible.
|
||||
|
||||
```bash
|
||||
# Fichier de configuration Tailwind
|
||||
tailwind.config.js
|
||||
|
||||
# Composants UI personnalisés
|
||||
components/ui/
|
||||
```
|
||||
|
||||
### 🔧 Configuration TypeScript
|
||||
|
||||
```bash
|
||||
# Configuration TypeScript
|
||||
tsconfig.json
|
||||
|
||||
# Types personnalisés
|
||||
types/
|
||||
```
|
||||
|
||||
### 📱 Configuration Responsive
|
||||
|
||||
L'interface s'adapte automatiquement aux différentes tailles d'écran :
|
||||
- 📱 Mobile (< 768px)
|
||||
- 📟 Tablet (768px - 1024px)
|
||||
- 💻 Desktop (> 1024px)
|
||||
|
||||
## 🔧 Commandes de Développement
|
||||
|
||||
### 🚀 Commandes Principales
|
||||
|
||||
```bash
|
||||
# Développement
|
||||
npm run dev # Serveur de développement (port 3000)
|
||||
npm run build # Build de production
|
||||
npm run start # Serveur de production
|
||||
npm run lint # Vérification du code
|
||||
|
||||
# Tests (si configurés)
|
||||
npm run test # Tests unitaires
|
||||
npm run test:watch # Tests en mode watch
|
||||
npm run test:coverage # Tests avec couverture
|
||||
|
||||
# Maintenance
|
||||
npm run clean # Nettoyer les fichiers temporaires
|
||||
npm run type-check # Vérification TypeScript
|
||||
```
|
||||
|
||||
### 🛠️ Commandes de Maintenance
|
||||
|
||||
```bash
|
||||
# Mise à jour des dépendances
|
||||
npm update # Mise à jour des paquets
|
||||
npm audit # Audit de sécurité
|
||||
npm audit fix # Correction automatique
|
||||
|
||||
# Gestion des dépendances
|
||||
npm install <package> # Installer un paquet
|
||||
npm uninstall <package> # Désinstaller un paquet
|
||||
npm list # Lister les paquets installés
|
||||
```
|
||||
|
||||
### 📦 Commandes de Build
|
||||
|
||||
```bash
|
||||
# Build de production
|
||||
npm run build
|
||||
|
||||
# Analyse du bundle
|
||||
npm run analyze # (si configuré)
|
||||
|
||||
# Build statique
|
||||
npm run export # (si configuré)
|
||||
```
|
||||
|
||||
### 🔍 Commandes de Debug
|
||||
|
||||
```bash
|
||||
# Logs détaillés
|
||||
DEBUG=* npm run dev
|
||||
|
||||
# Profiling
|
||||
npm run dev -- --profile
|
||||
|
||||
# Inspection du bundle
|
||||
npm run build -- --debug
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### 📖 Guides Disponibles
|
||||
|
||||
- **[Installation](docs/INSTALLATION.md)** - Guide d'installation complet
|
||||
- **[Configuration](docs/CONFIGURATION.md)** - Configuration avancée
|
||||
- **[Architecture](docs/ARCHITECTURE.md)** - Architecture technique
|
||||
- **[API](docs/API.md)** - Documentation des APIs
|
||||
- **[Sécurité](docs/SECURITY_AUDIT.md)** - Audit de sécurité
|
||||
- **[Utilisation](docs/USAGE.md)** - Guide d'utilisation
|
||||
|
||||
### 🔗 Ressources Externes
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs)
|
||||
- [Tailwind CSS](https://tailwindcss.com/docs)
|
||||
- [Radix UI](https://www.radix-ui.com/docs)
|
||||
- [TypeScript Handbook](https://www.typescriptlang.org/docs/)
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
### 📁 Structure du Projet
|
||||
|
||||
```
|
||||
docv/
|
||||
├── app/ # Pages et routes Next.js 13+
|
||||
│ ├── dashboard/ # Interface utilisateur
|
||||
│ ├── login/ # Authentification
|
||||
│ ├── formation/ # Module formation
|
||||
│ └── contact/ # Contact
|
||||
├── components/ # Composants réutilisables
|
||||
│ ├── ui/ # Composants UI de base
|
||||
│ └── 4nk/ # Composants spécifiques 4NK
|
||||
├── lib/ # Utilitaires et logique métier
|
||||
│ ├── 4nk/ # Modules 4NK
|
||||
│ └── utils.ts # Fonctions utilitaires
|
||||
├── public/ # Assets statiques
|
||||
├── styles/ # Styles globaux
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
### 🔧 Technologies Utilisées
|
||||
|
||||
| Technologie | Version | Description |
|
||||
|-------------|---------|-------------|
|
||||
| **Next.js** | 15.2.4 | Framework React full-stack |
|
||||
| **React** | 19.1.1 | Bibliothèque UI |
|
||||
| **TypeScript** | 5.0+ | Typage statique |
|
||||
| **Tailwind CSS** | 4.1.9 | Framework CSS |
|
||||
| **Radix UI** | Latest | Composants accessibles |
|
||||
| **Lucide React** | 0.454.0 | Icônes |
|
||||
| **Zod** | 3.25.67 | Validation de schémas |
|
||||
|
||||
### 🌐 Architecture de Sécurité
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
A[Client] --> B[Next.js App]
|
||||
B --> C[Authentification Cryptographique]
|
||||
C --> D[Base de Données Locale]
|
||||
D --> E[Chiffrement Bout en Bout]
|
||||
E --> F[Ancrage Blockchain]
|
||||
|
||||
G[IA Locale] --> H[Traitement OCR]
|
||||
H --> I[Classification]
|
||||
I --> J[Extraction de Données]
|
||||
```
|
||||
|
||||
## 🔒 Sécurité
|
||||
|
||||
### 🛡️ Mesures de Sécurité Implémentées
|
||||
|
||||
- ✅ **Authentification sans mot de passe** - Clés cryptographiques locales
|
||||
- ✅ **Chiffrement bout en bout** - Données protégées en transit et au repos
|
||||
- ✅ **Aucune interface admin** - Pas d'accès privilégié exposé
|
||||
- ✅ **Conformité réglementaire** - RGPD, ISO 27001, SecNumCloud
|
||||
- ✅ **Audit de sécurité** - Voir [SECURITY_AUDIT.md](docs/SECURITY_AUDIT.md)
|
||||
|
||||
### 🔐 Bonnes Pratiques
|
||||
|
||||
1. **Variables d'environnement** - Jamais de secrets en dur
|
||||
2. **Validation des données** - Schémas Zod pour toutes les entrées
|
||||
3. **HTTPS obligatoire** - En production uniquement
|
||||
4. **Audit régulier** - `npm audit` avant chaque déploiement
|
||||
|
||||
## 🤝 Contribution
|
||||
|
||||
### 🚀 Comment Contribuer
|
||||
|
||||
1. **Fork** le repository
|
||||
2. **Créer** une branche feature (`git checkout -b feature/amazing-feature`)
|
||||
3. **Commit** vos changements (`git commit -m 'Add amazing feature'`)
|
||||
4. **Push** vers la branche (`git push origin feature/amazing-feature`)
|
||||
5. **Ouvrir** une Pull Request
|
||||
|
||||
### 📝 Standards de Code
|
||||
|
||||
- **TypeScript** strict activé
|
||||
- **ESLint** pour la qualité du code
|
||||
- **Prettier** pour le formatage
|
||||
- **Conventional Commits** pour les messages
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
```bash
|
||||
# Avant de contribuer, assurez-vous que :
|
||||
npm run lint # ✅ Pas d'erreurs ESLint
|
||||
npm run type-check # ✅ Pas d'erreurs TypeScript
|
||||
npm run build # ✅ Build réussi
|
||||
```
|
||||
|
||||
## 📞 Support
|
||||
|
||||
### 🆘 Obtenir de l'Aide
|
||||
|
||||
- **📧 Email** : contact@docv.fr
|
||||
- **📚 Documentation** : [docs/](docs/)
|
||||
- **🐛 Issues** : [GitHub Issues](https://github.com/your-org/docv/issues)
|
||||
- **💬 Discussions** : [GitHub Discussions](https://github.com/your-org/docv/discussions)
|
||||
|
||||
### 🏢 Entreprise
|
||||
|
||||
**4NK** - Pionnier du Web 5.0
|
||||
- 🏢 Solutions de souveraineté
|
||||
- 🔒 Sécurité de bout en bout
|
||||
- 🌐 Architecture distribuée
|
||||
|
||||
### 📋 Checklist de Support
|
||||
|
||||
Avant de demander de l'aide, vérifiez :
|
||||
|
||||
- [ ] Version de Node.js compatible (18.0+)
|
||||
- [ ] Dépendances installées (`npm install`)
|
||||
- [ ] Variables d'environnement configurées
|
||||
- [ ] Logs d'erreur consultés
|
||||
- [ ] Documentation parcourue
|
||||
|
||||
---
|
||||
|
||||
## 📄 Licence
|
||||
|
||||
Ce projet est propriétaire et confidentiel. Tous droits réservés à **4NK**.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🛡️ DocV - Sécurisez votre entreprise avec la GED simple et souveraine**
|
||||
|
||||
[](https://4nkweb.com)
|
||||
[](mailto:contact@docv.fr)
|
||||
|
||||
</div>
|
||||
16
app/actions/users.ts
Normal file
16
app/actions/users.ts
Normal file
@ -0,0 +1,16 @@
|
||||
"use server"
|
||||
|
||||
import { sendUserInviteEmail } from "@/lib/email"
|
||||
|
||||
export async function sendInviteEmailAction(params: { email: string; role: string; words: string[]; code: string; resourceTitle?: string; link?: string }) {
|
||||
try {
|
||||
const { email, link, role, words, code, resourceTitle } = params
|
||||
if (!email || !role || !words || words.length !== 4 || !code) {
|
||||
return { success: false, error: "Paramètres manquants" }
|
||||
}
|
||||
const res = await sendUserInviteEmail({ recipientEmail: email, role, words, code, resourceTitle, inviteLink: link })
|
||||
return res
|
||||
} catch (e: any) {
|
||||
return { success: false, error: e.message }
|
||||
}
|
||||
}
|
||||
@ -1,607 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import {
|
||||
MessageSquare,
|
||||
Search,
|
||||
Plus,
|
||||
Send,
|
||||
Paperclip,
|
||||
Smile,
|
||||
Phone,
|
||||
Video,
|
||||
MoreHorizontal,
|
||||
Users,
|
||||
Circle,
|
||||
CheckCheck,
|
||||
Clock,
|
||||
File,
|
||||
Download,
|
||||
Brain,
|
||||
Shield,
|
||||
TrendingUp,
|
||||
CheckCircle,
|
||||
FileText,
|
||||
BarChart3,
|
||||
Zap,
|
||||
} from "lucide-react"
|
||||
import { useSearchParams } from "next/navigation"
|
||||
import Chat from "@/components/4nk/Chat"
|
||||
|
||||
export default function ChatPage() {
|
||||
const [selectedConversation, setSelectedConversation] = useState("1")
|
||||
const [newMessage, setNewMessage] = useState("")
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const userId = searchParams.get("user")
|
||||
const messageType = searchParams.get("message")
|
||||
const groupType = searchParams.get("type")
|
||||
|
||||
useEffect(() => {
|
||||
// Gérer les nouveaux messages depuis les autres pages
|
||||
if (messageType === "new") {
|
||||
if (userId) {
|
||||
// Message individuel
|
||||
const messageData = sessionStorage.getItem("newMessage")
|
||||
if (messageData) {
|
||||
const data = JSON.parse(messageData)
|
||||
console.log("Nouveau message individuel:", data)
|
||||
|
||||
// Créer ou ouvrir la conversation avec cet utilisateur
|
||||
setSelectedConversation(userId)
|
||||
|
||||
// Ajouter le message pré-rempli
|
||||
setNewMessage(`${data.subject ? `[${data.subject}] ` : ""}${data.content}`)
|
||||
|
||||
// Nettoyer le sessionStorage
|
||||
sessionStorage.removeItem("newMessage")
|
||||
|
||||
// Notification
|
||||
showNotification("info", `Conversation ouverte avec ${data.userName}`)
|
||||
}
|
||||
} else if (groupType === "group") {
|
||||
// Message de groupe
|
||||
const groupData = sessionStorage.getItem("newGroupMessage")
|
||||
if (groupData) {
|
||||
const data = JSON.parse(groupData)
|
||||
console.log("Nouveau message de groupe:", data)
|
||||
|
||||
// Créer une nouvelle conversation de groupe
|
||||
const groupName = `Groupe (${data.users.length} membres)`
|
||||
setSelectedConversation("group-new")
|
||||
|
||||
// Ajouter le message pré-rempli
|
||||
setNewMessage(`${data.subject ? `[${data.subject}] ` : ""}${data.content}`)
|
||||
|
||||
// Nettoyer le sessionStorage
|
||||
sessionStorage.removeItem("newGroupMessage")
|
||||
|
||||
// Notification
|
||||
showNotification("info", `Conversation de groupe créée avec ${data.users.length} utilisateur(s)`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [userId, messageType, groupType])
|
||||
|
||||
const showNotification = (type: "success" | "error" | "info", message: string) => {
|
||||
// Implémenter la notification (peut utiliser toast ou état local)
|
||||
console.log(`${type.toUpperCase()}: ${message}`)
|
||||
}
|
||||
|
||||
const conversations = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Marie Dubois",
|
||||
type: "direct",
|
||||
avatar: "MD",
|
||||
lastMessage: "Parfait, merci pour la validation !",
|
||||
lastMessageTime: "14:32",
|
||||
unreadCount: 0,
|
||||
isOnline: true,
|
||||
isTyping: false,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Équipe Juridique",
|
||||
type: "group",
|
||||
avatar: "EJ",
|
||||
lastMessage: "IA DocV: Analyse terminée pour Contrat_Client_ABC.pdf",
|
||||
lastMessageTime: "13:45",
|
||||
unreadCount: 1,
|
||||
isOnline: false,
|
||||
isTyping: false,
|
||||
members: 5,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "Sophie Laurent",
|
||||
type: "direct",
|
||||
avatar: "SL",
|
||||
lastMessage: "Pouvez-vous m'envoyer le rapport ?",
|
||||
lastMessageTime: "12:20",
|
||||
unreadCount: 1,
|
||||
isOnline: false,
|
||||
isTyping: false,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "Direction",
|
||||
type: "group",
|
||||
avatar: "DIR",
|
||||
lastMessage: "Réunion reportée à demain 10h",
|
||||
lastMessageTime: "11:15",
|
||||
unreadCount: 0,
|
||||
isOnline: false,
|
||||
isTyping: false,
|
||||
members: 3,
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "Thomas Rousseau",
|
||||
type: "direct",
|
||||
avatar: "TR",
|
||||
lastMessage: "Merci pour l'info !",
|
||||
lastMessageTime: "Hier",
|
||||
unreadCount: 0,
|
||||
isOnline: true,
|
||||
isTyping: true,
|
||||
},
|
||||
]
|
||||
|
||||
const messages = [
|
||||
{
|
||||
id: "1",
|
||||
senderId: "marie",
|
||||
senderName: "Marie Dubois",
|
||||
content: "Bonjour ! J'ai besoin de votre avis sur le nouveau contrat client.",
|
||||
timestamp: "14:20",
|
||||
type: "text",
|
||||
status: "read",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
senderId: "me",
|
||||
senderName: "Moi",
|
||||
content: "Bien sûr, pouvez-vous me l'envoyer ?",
|
||||
timestamp: "14:22",
|
||||
type: "text",
|
||||
status: "read",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
senderId: "marie",
|
||||
senderName: "Marie Dubois",
|
||||
content: "",
|
||||
timestamp: "14:25",
|
||||
type: "file",
|
||||
fileName: "Contrat_Client_ABC.pdf",
|
||||
fileSize: "2.3 MB",
|
||||
status: "read",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
senderId: "me",
|
||||
senderName: "Moi",
|
||||
content: "J'ai relu le contrat, tout me semble correct. Les clauses de confidentialité sont bien définies.",
|
||||
timestamp: "14:30",
|
||||
type: "text",
|
||||
status: "read",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
senderId: "marie",
|
||||
senderName: "Marie Dubois",
|
||||
content: "Parfait, merci pour la validation !",
|
||||
timestamp: "14:32",
|
||||
type: "text",
|
||||
status: "delivered",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
senderId: "ai",
|
||||
senderName: "IA DocV",
|
||||
content: `📄 **Analyse IA du document "Contrat_Client_ABC.pdf"**
|
||||
|
||||
**Type de document :** PDF (2.3 MB)
|
||||
**Statut :** ✅ Validé
|
||||
**Dernière modification :** Il y a 2 heures
|
||||
|
||||
**📊 Analyse du contenu :**
|
||||
• Document juridique détecté avec haute précision
|
||||
• 3 tag(s) identifié(s) : contrat, client, juridique
|
||||
• Résumé automatique disponible
|
||||
• 47 pages analysées
|
||||
• 12 clauses contractuelles détectées
|
||||
|
||||
**🎯 Métriques de qualité :**
|
||||
• Lisibilité : 92%
|
||||
• Conformité juridique : 100%
|
||||
• Sécurité documentaire : Maximale
|
||||
• Complétude des informations : 95%
|
||||
|
||||
**🔍 Points clés identifiés :**
|
||||
• Durée du contrat : 12 mois
|
||||
• Montant total : 150 000€ HT
|
||||
• Clauses de confidentialité : ✅ Présentes et conformes
|
||||
• Propriété intellectuelle : ✅ Bien définie
|
||||
• Conditions de résiliation : ✅ Équilibrées
|
||||
|
||||
**🛡️ Analyse de conformité RGPD :**
|
||||
• Données personnelles : ⚠️ Détectées (coordonnées client)
|
||||
• Durée de conservation : Conforme (7 ans)
|
||||
• Droit à l'oubli : Applicable après expiration
|
||||
• Consentement : ✅ Explicite
|
||||
|
||||
**⚡ Recommandations :**
|
||||
• ✅ Document prêt pour signature
|
||||
• 📋 Archivage permanent recommandé
|
||||
• 🔄 Révision suggérée dans 11 mois
|
||||
• 📧 Notification client automatique activée
|
||||
|
||||
**📈 Score global : 94/100**
|
||||
|
||||
*Analyse générée automatiquement par l'IA DocV - Fiabilité : 98%*`,
|
||||
timestamp: "14:35",
|
||||
type: "ai_analysis",
|
||||
status: "delivered",
|
||||
analysisType: "document",
|
||||
documentName: "Contrat_Client_ABC.pdf",
|
||||
confidence: 98,
|
||||
processingTime: "2.3s",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
senderId: "ai",
|
||||
senderName: "IA DocV",
|
||||
content: `🔍 **Analyse comparative - Dossier Contrats**
|
||||
|
||||
**📊 Analyse de 8 documents similaires :**
|
||||
• Contrats clients : 5 documents
|
||||
• Avenants : 2 documents
|
||||
• Conditions générales : 1 document
|
||||
|
||||
**📈 Tendances identifiées :**
|
||||
• Montant moyen des contrats : +15% vs trimestre précédent
|
||||
• Durée moyenne : 14 mois (stable)
|
||||
• Taux de renouvellement : 87% (↗️ +5%)
|
||||
|
||||
**⚠️ Points d'attention :**
|
||||
• 2 contrats expirent dans les 30 jours
|
||||
• 1 clause de révision tarifaire à activer
|
||||
• Mise à jour RGPD requise sur 3 documents
|
||||
|
||||
**🎯 Actions recommandées :**
|
||||
1. Planifier renouvellement contrats Q1 2024
|
||||
2. Standardiser les clauses de confidentialité
|
||||
3. Créer un modèle basé sur ce contrat (performance optimale)
|
||||
|
||||
*Analyse prédictive activée - Prochaine révision : 15 février 2024*`,
|
||||
timestamp: "14:37",
|
||||
type: "ai_analysis",
|
||||
status: "delivered",
|
||||
analysisType: "comparative",
|
||||
confidence: 95,
|
||||
processingTime: "4.1s",
|
||||
},
|
||||
]
|
||||
|
||||
const filteredConversations = conversations.filter((conv) =>
|
||||
conv.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
)
|
||||
|
||||
const currentConversation = conversations.find((conv) => conv.id === selectedConversation)
|
||||
|
||||
const handleSendMessage = () => {
|
||||
if (newMessage.trim()) {
|
||||
// Ici on ajouterait la logique pour envoyer le message
|
||||
console.log("Sending message:", newMessage)
|
||||
setNewMessage("")
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case "sent":
|
||||
return <Clock className="h-3 w-3 text-gray-400" />
|
||||
case "delivered":
|
||||
return <CheckCheck className="h-3 w-3 text-gray-400" />
|
||||
case "read":
|
||||
return <CheckCheck className="h-3 w-3 text-blue-500" />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const getAnalysisIcon = (analysisType: string) => {
|
||||
switch (analysisType) {
|
||||
case "document":
|
||||
return <FileText className="h-4 w-4" />
|
||||
case "comparative":
|
||||
return <BarChart3 className="h-4 w-4" />
|
||||
case "security":
|
||||
return <Shield className="h-4 w-4" />
|
||||
case "performance":
|
||||
return <TrendingUp className="h-4 w-4" />
|
||||
default:
|
||||
return <Brain className="h-4 w-4" />
|
||||
}
|
||||
}
|
||||
|
||||
const renderAIMessage = (message: any) => {
|
||||
return (
|
||||
<div className="flex justify-start">
|
||||
<div className="max-w-4xl">
|
||||
{/* AI Header */}
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<div className="w-8 h-8 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full flex items-center justify-center">
|
||||
<Brain className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm font-medium text-gray-900">IA DocV</span>
|
||||
<Badge className="bg-gradient-to-r from-purple-100 to-blue-100 text-purple-700 border-purple-200 text-xs">
|
||||
{getAnalysisIcon(message.analysisType)}
|
||||
<span className="ml-1">
|
||||
{message.analysisType === "document"
|
||||
? "Analyse Document"
|
||||
: message.analysisType === "comparative"
|
||||
? "Analyse Comparative"
|
||||
: "Analyse IA"}
|
||||
</span>
|
||||
</Badge>
|
||||
{message.confidence && (
|
||||
<Badge className="bg-green-100 text-green-700 border-green-200 text-xs">
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
{message.confidence}% fiable
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI Message Content */}
|
||||
<div className="bg-gradient-to-r from-purple-50 to-blue-50 border border-purple-200 rounded-lg p-4 shadow-sm">
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<div className="whitespace-pre-line text-gray-800 leading-relaxed">{message.content}</div>
|
||||
</div>
|
||||
|
||||
{/* AI Message Footer */}
|
||||
<div className="flex items-center justify-between mt-4 pt-3 border-t border-purple-200">
|
||||
<div className="flex items-center space-x-4 text-xs text-gray-600">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Zap className="h-3 w-3" />
|
||||
<span>Traité en {message.processingTime}</span>
|
||||
</div>
|
||||
{message.documentName && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<FileText className="h-3 w-3" />
|
||||
<span>{message.documentName}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-xs text-purple-600">{message.timestamp}</span>
|
||||
<div>{getStatusIcon(message.status)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-[calc(100vh-8rem)] flex">
|
||||
{/* Sidebar - Conversations */}
|
||||
<div className="w-80 border-r bg-white flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="p-4 border-b">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold">Messages</h2>
|
||||
<Button size="sm">
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||
<Input
|
||||
placeholder="Rechercher une conversation..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Conversations List */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{filteredConversations.map((conversation) => (
|
||||
<div
|
||||
key={conversation.id}
|
||||
onClick={() => setSelectedConversation(conversation.id)}
|
||||
className={`p-4 border-b cursor-pointer hover:bg-gray-50 ${
|
||||
selectedConversation === conversation.id ? "bg-blue-50 border-r-2 border-blue-500" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="relative">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
{conversation.type === "group" ? (
|
||||
<Users className="h-6 w-6 text-blue-600" />
|
||||
) : (
|
||||
<span className="text-blue-600 font-medium">{conversation.avatar}</span>
|
||||
)}
|
||||
</div>
|
||||
{conversation.isOnline && conversation.type === "direct" && (
|
||||
<Circle className="absolute -bottom-1 -right-1 h-4 w-4 text-green-500 fill-current" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-medium text-gray-900 truncate">{conversation.name}</h3>
|
||||
<span className="text-xs text-gray-500">{conversation.lastMessageTime}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-1">
|
||||
<p className="text-sm text-gray-600 truncate">
|
||||
{conversation.isTyping ? (
|
||||
<span className="text-blue-600 italic">En train d'écrire...</span>
|
||||
) : (
|
||||
<span
|
||||
className={conversation.lastMessage.includes("IA DocV:") ? "text-purple-600 font-medium" : ""}
|
||||
>
|
||||
{conversation.lastMessage}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
{conversation.unreadCount > 0 && (
|
||||
<Badge
|
||||
className={`text-white text-xs px-2 py-1 rounded-full ${
|
||||
conversation.lastMessage.includes("IA DocV:") ? "bg-purple-600" : "bg-blue-600"
|
||||
}`}
|
||||
>
|
||||
{conversation.unreadCount}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{conversation.type === "group" && (
|
||||
<p className="text-xs text-gray-500 mt-1">{conversation.members} membres</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Chat Area */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{currentConversation ? (
|
||||
<>
|
||||
{/* Chat Header */}
|
||||
<div className="p-4 border-b bg-white">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative">
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
{currentConversation.type === "group" ? (
|
||||
<Users className="h-5 w-5 text-blue-600" />
|
||||
) : (
|
||||
<span className="text-blue-600 font-medium">{currentConversation.avatar}</span>
|
||||
)}
|
||||
</div>
|
||||
{currentConversation.isOnline && currentConversation.type === "direct" && (
|
||||
<Circle className="absolute -bottom-1 -right-1 h-3 w-3 text-green-500 fill-current" />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900">{currentConversation.name}</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
{currentConversation.type === "group"
|
||||
? `${currentConversation.members} membres`
|
||||
: currentConversation.isOnline
|
||||
? "En ligne"
|
||||
: "Hors ligne"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<Phone className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<Video className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Messages */}
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50">
|
||||
{messages.map((message) => (
|
||||
<div key={message.id}>
|
||||
{message.type === "ai_analysis" ? (
|
||||
renderAIMessage(message)
|
||||
) : (
|
||||
<div className={`flex ${message.senderId === "me" ? "justify-end" : "justify-start"}`}>
|
||||
<div
|
||||
className={`max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${
|
||||
message.senderId === "me" ? "bg-blue-600 text-white" : "bg-white text-gray-900 shadow-sm"
|
||||
}`}
|
||||
>
|
||||
{message.type === "text" ? (
|
||||
<p className="text-sm">{message.content}</p>
|
||||
) : message.type === "file" ? (
|
||||
<div className="flex items-center space-x-3 p-2">
|
||||
<File className="h-8 w-8 text-gray-400" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">{message.fileName}</p>
|
||||
<p className="text-xs text-gray-500">{message.fileSize}</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
className={`flex items-center justify-between mt-1 ${
|
||||
message.senderId === "me" ? "text-blue-100" : "text-gray-500"
|
||||
}`}
|
||||
>
|
||||
<span className="text-xs">{message.timestamp}</span>
|
||||
{message.senderId === "me" && <div className="ml-2">{getStatusIcon(message.status)}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Message Input */}
|
||||
<div className="p-4 border-t bg-white">
|
||||
<div className="flex items-end space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<Paperclip className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex-1">
|
||||
<Textarea
|
||||
placeholder="Tapez votre message..."
|
||||
value={newMessage}
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
handleSendMessage()
|
||||
}
|
||||
}}
|
||||
rows={1}
|
||||
className="resize-none"
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="sm">
|
||||
<Smile className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button onClick={handleSendMessage} disabled={!newMessage.trim()}>
|
||||
<Send className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center bg-gray-50">
|
||||
<div className="text-center">
|
||||
<MessageSquare className="h-12 w-12 mx-auto text-gray-400 mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Sélectionnez une conversation</h3>
|
||||
<p className="text-gray-600">Choisissez une conversation pour commencer à discuter</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
return <Chat heightClass="h-[calc(100vh-8rem)]" />
|
||||
}
|
||||
|
||||
@ -587,6 +587,64 @@ export default function DocumentsPage() {
|
||||
canAnalyze: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "Note_Projet_Nouveau.docx",
|
||||
type: "DOCX",
|
||||
size: "0.8 MB",
|
||||
modified: new Date(),
|
||||
created: new Date(),
|
||||
author: "Utilisateur actuel",
|
||||
folder: "Nouveaux Dossiers",
|
||||
folderId: "new-folders",
|
||||
tags: ["nouveau", "projet"],
|
||||
status: "draft",
|
||||
thumbnail: "/placeholder.svg?height=120&width=120&text=DOCX",
|
||||
description: "Documentation initiale d'un nouveau projet.",
|
||||
version: "v0.1",
|
||||
isValidated: false,
|
||||
hasCertificate: false,
|
||||
storageType: "temporary",
|
||||
summary: "Note de cadrage et premiers éléments du projet.",
|
||||
permissions: {
|
||||
canView: true,
|
||||
canEdit: true,
|
||||
canDelete: true,
|
||||
canInvite: true,
|
||||
canValidate: false,
|
||||
canArchive: true,
|
||||
canAnalyze: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "Budget_Preliminaire.xlsx",
|
||||
type: "XLSX",
|
||||
size: "0.3 MB",
|
||||
modified: new Date(),
|
||||
created: new Date(),
|
||||
author: "Utilisateur actuel",
|
||||
folder: "Lancements",
|
||||
folderId: "launch",
|
||||
tags: ["nouveau", "budget"],
|
||||
status: "pending",
|
||||
thumbnail: "/placeholder.svg?height=120&width=120&text=XLSX",
|
||||
description: "Budget préliminaire pour un nouveau dossier.",
|
||||
version: "v0.1",
|
||||
isValidated: false,
|
||||
hasCertificate: false,
|
||||
storageType: "temporary",
|
||||
summary: "Prévisions de coûts et enveloppe initiale.",
|
||||
permissions: {
|
||||
canView: true,
|
||||
canEdit: true,
|
||||
canDelete: true,
|
||||
canInvite: true,
|
||||
canValidate: false,
|
||||
canArchive: true,
|
||||
canAnalyze: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
setDocuments(mockDocuments)
|
||||
@ -1419,6 +1477,13 @@ Message : ${requestMessage || "Aucun message spécifique"}`,
|
||||
}
|
||||
}
|
||||
|
||||
const isNewDocument = (doc: DocumentData) => {
|
||||
const now = Date.now()
|
||||
const diffMs = now - doc.modified.getTime()
|
||||
const twoDaysMs = 2 * 24 * 60 * 60 * 1000
|
||||
return diffMs <= twoDaysMs
|
||||
}
|
||||
|
||||
const getStorageIcon = (storageType: string) => {
|
||||
return storageType === "permanent" ? (
|
||||
<Cloud className="h-4 w-4 text-blue-600" title="Stockage permanent" />
|
||||
@ -1584,15 +1649,7 @@ Message : ${requestMessage || "Aucun message spécifique"}`,
|
||||
<CardContent className="p-4">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center space-y-3 sm:space-y-0 sm:space-x-4">
|
||||
<div className="relative flex-1 sm:w-80">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="Rechercher des documents..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowFilters(!showFilters)}
|
||||
@ -1809,7 +1866,7 @@ Message : ${requestMessage || "Aucun message spécifique"}`,
|
||||
}}
|
||||
>
|
||||
<HardDrive className="h-4 w-4 mr-2" />
|
||||
Changer de storage
|
||||
Conservation
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -1824,7 +1881,7 @@ Message : ${requestMessage || "Aucun message spécifique"}`,
|
||||
}}
|
||||
>
|
||||
<ShieldCheck className="h-4 w-4 mr-2" />
|
||||
Télécharger certificats
|
||||
Certificats
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -1839,7 +1896,7 @@ Message : ${requestMessage || "Aucun message spécifique"}`,
|
||||
}}
|
||||
>
|
||||
<Users className="h-4 w-4 mr-2" />
|
||||
Configurer les rôles
|
||||
Rôles
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -1884,6 +1941,9 @@ Message : ${requestMessage || "Aucun message spécifique"}`,
|
||||
{getFileIcon(doc.type)}
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="font-medium text-gray-900">{doc.name}</span>
|
||||
{isNewDocument(doc) && (
|
||||
<Badge className="bg-blue-100 text-blue-700 border-blue-200">NEW</Badge>
|
||||
)}
|
||||
{getStorageIcon(doc.storageType)}
|
||||
{doc.isValidated && <ShieldCheck className="h-4 w-4 text-green-600" />}
|
||||
{doc.temporaryStorageConfig && (
|
||||
@ -1923,6 +1983,9 @@ Message : ${requestMessage || "Aucun message spécifique"}`,
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute top-2 right-2 flex items-center space-x-1">
|
||||
{isNewDocument(doc) && (
|
||||
<Badge className="bg-blue-100 text-blue-700 border-blue-200">NEW</Badge>
|
||||
)}
|
||||
{getStorageIcon(doc.storageType)}
|
||||
{doc.isValidated && <ShieldCheck className="h-4 w-4 text-green-600" />}
|
||||
{doc.temporaryStorageConfig && (
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useState, useEffect, useCallback } from "react"
|
||||
import { useRouter, usePathname } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@ -23,40 +23,51 @@ import {
|
||||
TestTube,
|
||||
ChevronRight,
|
||||
Home,
|
||||
Key,
|
||||
} from "lucide-react"
|
||||
import AuthModal from "@/components/4nk/AuthModal"
|
||||
import MessageBus from "@/lib/4nk/MessageBus"
|
||||
import UserStore from "@/lib/4nk/UserStore"
|
||||
import Iframe from "@/components/4nk/Iframe"
|
||||
import EventBus from "@/lib/4nk/EventBus"
|
||||
import { iframeUrl } from "../page"
|
||||
import EventBus from "@/lib/4nk/EventBus"
|
||||
|
||||
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false)
|
||||
const [processes, setProcesses] = useState<any>(null)
|
||||
const [myProcesses, setMyProcesses] = useState<string[]>([])
|
||||
const [isConnected, setIsConnected] = useState(false)
|
||||
const [userPairingId, setUserPairingId] = useState<string | null>(null)
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isMockMode, setIsMockMode] = useState(false)
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||
const [userInfo, setUserInfo] = useState<any>(null)
|
||||
const [showLogoutConfirm, setShowLogoutConfirm] = useState(false)
|
||||
|
||||
const [isPrivateKeyFlash, setIsPrivateKeyFlash] = useState(false)
|
||||
const [currentFolderType, setCurrentFolderType] = useState<string | undefined>(undefined)
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
const navigation = [
|
||||
{ name: "Tableau de bord", href: "/dashboard", icon: LayoutDashboard },
|
||||
{ name: "Documents", href: "/dashboard/documents", icon: FileText },
|
||||
{ name: "Dossiers", href: "/dashboard/folders", icon: Folder },
|
||||
{ name: "Recherche", href: "/dashboard/search", icon: Search },
|
||||
{ name: "Utilisateurs", href: "/dashboard/users", icon: Users },
|
||||
{ name: "Messages", href: "/dashboard/chat", icon: MessageSquare },
|
||||
{ name: "Paramètres", href: "/dashboard/settings", icon: Settings },
|
||||
const navigation: Array<{ name: string; href: string; icon: any; type?: string }> = [
|
||||
{ name: "My work", href: "/dashboard", icon: LayoutDashboard },
|
||||
{ name: "Contrats", href: "/dashboard/folders?type=contracts", icon: Folder, type: "contracts" },
|
||||
{ name: "Rapports", href: "/dashboard/folders?type=reports", icon: Folder, type: "reports" },
|
||||
{ name: "Projets", href: "/dashboard/folders?type=projects", icon: Folder, type: "projects" },
|
||||
{ name: "Finance", href: "/dashboard/folders?type=finance", icon: Folder, type: "finance" },
|
||||
{ name: "RH", href: "/dashboard/folders?type=hr", icon: Folder, type: "hr" },
|
||||
{ name: "Marketing", href: "/dashboard/folders?type=marketing", icon: Folder, type: "marketing" },
|
||||
{ name: "Juridique", href: "/dashboard/folders?type=legal", icon: Folder, type: "legal" },
|
||||
{ name: "Général", href: "/dashboard/folders?type=general", icon: Folder, type: "general" },
|
||||
]
|
||||
|
||||
// Appliquer le thème global dès le chargement (préférence stockée)
|
||||
useEffect(() => {
|
||||
try {
|
||||
const saved = typeof window !== 'undefined' ? localStorage.getItem('theme') : null
|
||||
const dark = saved ? saved === 'dark' : (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
if (typeof document !== 'undefined') {
|
||||
document.documentElement.classList.toggle('dark', !!dark)
|
||||
}
|
||||
} catch { }
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const connected = UserStore.getInstance().isConnected();
|
||||
console.log('[Login] User connected:', connected);
|
||||
@ -104,94 +115,100 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
const handleLogout = useCallback(() => {
|
||||
UserStore.getInstance().disconnect();
|
||||
setIsConnected(false);
|
||||
setProcesses(null);
|
||||
setMyProcesses([]);
|
||||
setUserPairingId(null);
|
||||
|
||||
// Émettre un événement pour vider aussi les messages locaux dans MessageConsole
|
||||
// Émettre un événement pour vider les messages locaux
|
||||
EventBus.getInstance().emit('CLEAR_CONSOLE');
|
||||
|
||||
// Vider les processes
|
||||
setProcesses(null);
|
||||
|
||||
// Afficher un message de confirmation avec options
|
||||
setShowLogoutConfirm(true)
|
||||
}
|
||||
|
||||
const confirmLogout = (goToHome = false) => {
|
||||
setShowLogoutConfirm(false)
|
||||
if (goToHome) {
|
||||
router.push("/")
|
||||
} else {
|
||||
router.push("/login")
|
||||
}, []);
|
||||
|
||||
// Stabilise la lecture de query params côté client pour éviter les décalages SSR/CSR
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const updateType = () => {
|
||||
const t = new URLSearchParams(window.location.search).get("type") || undefined
|
||||
setCurrentFolderType(t || undefined)
|
||||
}
|
||||
updateType()
|
||||
window.addEventListener("popstate", updateType)
|
||||
return () => window.removeEventListener("popstate", updateType)
|
||||
}
|
||||
}
|
||||
}, [pathname])
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Shield className="h-12 w-12 mx-auto mb-4 text-blue-600 animate-pulse" />
|
||||
<p className="text-gray-600">Vérification de l'authentification...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
useEffect(() => {
|
||||
const onPrivateKeyAccess = () => {
|
||||
setIsPrivateKeyFlash(true)
|
||||
setTimeout(() => setIsPrivateKeyFlash(false), 400)
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("private-key-access", onPrivateKeyAccess as EventListener)
|
||||
}
|
||||
return () => {
|
||||
if (typeof window !== "undefined") {
|
||||
window.removeEventListener("private-key-access", onPrivateKeyAccess as EventListener)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<AuthModal
|
||||
isOpen={isAuthModalOpen}
|
||||
onConnect={handleAuthSuccess}
|
||||
onClose={() => router.push("/login")}
|
||||
iframeUrl={iframeUrl}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
// Suppression des retours conditionnels précoces pour stabiliser l'ordre des hooks
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-gray-50">
|
||||
<div className="flex h-screen bg-gray-50 dark:bg-gray-900">
|
||||
{/* Sidebar mobile overlay */}
|
||||
{sidebarOpen && (
|
||||
<div className="fixed inset-0 z-40 lg:hidden">
|
||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-75" onClick={() => setSidebarOpen(false)} />
|
||||
<div
|
||||
className="fixed inset-0 bg-gray-600 bg-opacity-75"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
className={`fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-lg transform transition-transform duration-300 ease-in-out lg:translate-x-0 lg:relative lg:flex lg:flex-col ${sidebarOpen ? "translate-x-0" : "-translate-x-full"}`}
|
||||
className={`fixed inset-y-0 left-0 z-50 w-64 bg-white dark:bg-gray-800 shadow-lg transform transition-transform duration-300 ease-in-out lg:translate-x-0 lg:relative lg:flex lg:flex-col ${sidebarOpen ? "translate-x-0" : "-translate-x-full"
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Logo */}
|
||||
<div className="flex items-center justify-between h-16 px-6 border-b">
|
||||
<div className="flex items-center justify-between h-16 px-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Shield className="h-8 w-8 text-blue-600" />
|
||||
<span className="text-xl font-bold text-gray-900">DocV</span>
|
||||
<Shield className="h-8 w-8 text-blue-600 dark:text-blue-400" />
|
||||
<span className="text-xl font-bold text-gray-900 dark:text-gray-100">DocV</span>
|
||||
{isMockMode && (
|
||||
<Badge variant="outline" className="bg-green-50 text-green-700 border-green-200 text-xs">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="bg-green-50 dark:bg-green-800 text-green-700 dark:text-green-200 border-green-200 dark:border-green-700 text-xs"
|
||||
>
|
||||
<TestTube className="h-3 w-3 mr-1" />
|
||||
Démo
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="lg:hidden" onClick={() => setSidebarOpen(false)}>
|
||||
<X className="h-5 w-5" />
|
||||
<X className="h-5 w-5 text-gray-900 dark:text-gray-100" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* User info */}
|
||||
{userInfo && (
|
||||
<div className="px-6 py-4 border-b bg-gray-50">
|
||||
<div className="px-6 py-4 border-b bg-gray-50 dark:bg-gray-700 border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-blue-600 font-medium text-sm">{userInfo.name.charAt(0)}</span>
|
||||
<div className="w-10 h-10 bg-blue-100 dark:bg-blue-800 rounded-full flex items-center justify-center">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-medium text-sm">
|
||||
{userInfo.name.charAt(0)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">{userInfo.name}</p>
|
||||
<p className="text-xs text-gray-500 truncate">{userInfo.company}</p>
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">{userInfo.name}</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 truncate">{userInfo.company}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -200,12 +217,16 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 px-4 py-4 space-y-1 overflow-y-auto">
|
||||
{navigation.map((item) => {
|
||||
const isActive = pathname === item.href
|
||||
const isActive =
|
||||
pathname === item.href ||
|
||||
(item.type && pathname.startsWith("/dashboard/folders") && currentFolderType === item.type);
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={`flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors ${isActive ? "bg-blue-100 text-blue-700" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
||||
className={`flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors ${isActive
|
||||
? "bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-400"
|
||||
: "text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100"
|
||||
}`}
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
>
|
||||
@ -213,21 +234,28 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
{item.name}
|
||||
{isActive && <ChevronRight className="h-4 w-4 ml-auto" />}
|
||||
</Link>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-4 border-t">
|
||||
<div className="p-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-xs text-gray-500">
|
||||
<div className="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>Sécurisé par 4NK</span>
|
||||
<Shield className="h-3 w-3" />
|
||||
</div>
|
||||
{isMockMode && (
|
||||
<div className="text-xs text-green-600 bg-green-50 p-2 rounded">Mode démonstration actif</div>
|
||||
<div className="text-xs text-green-600 dark:text-green-300 bg-green-50 dark:bg-green-800 p-2 rounded">
|
||||
Mode démonstration actif
|
||||
</div>
|
||||
)}
|
||||
<Button variant="outline" size="sm" onClick={handleLogout} className="w-full bg-transparent">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleLogout}
|
||||
className="w-full bg-transparent dark:bg-transparent"
|
||||
>
|
||||
<LogOut className="h-4 w-4 mr-2" />
|
||||
Déconnexion
|
||||
</Button>
|
||||
@ -239,23 +267,29 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
{/* Main content */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Top bar */}
|
||||
<div className="bg-white border-b px-4 py-3 shadow-sm">
|
||||
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 py-3 shadow-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Button variant="ghost" size="sm" className="lg:hidden" onClick={() => setSidebarOpen(true)}>
|
||||
<Menu className="h-5 w-5" />
|
||||
<Menu className="h-5 w-5 text-gray-900 dark:text-gray-100" />
|
||||
</Button>
|
||||
|
||||
<div className="hidden lg:block">
|
||||
<nav className="flex space-x-1 text-sm text-gray-500">
|
||||
<Link href="/dashboard" className="hover:text-gray-700">
|
||||
Tableau de bord
|
||||
<nav className="flex space-x-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<Link href="/dashboard" className="hover:text-gray-700 dark:hover:text-gray-100">
|
||||
My work
|
||||
</Link>
|
||||
{pathname !== "/dashboard" && (
|
||||
<>
|
||||
<ChevronRight className="h-4 w-4 mx-1" />
|
||||
<span className="text-gray-900 font-medium">
|
||||
{navigation.find((item) => item.href === pathname)?.name || "Page"}
|
||||
<ChevronRight className="h-4 w-4 mx-1 text-gray-500 dark:text-gray-400" />
|
||||
<span className="text-gray-900 dark:text-gray-100 font-medium">
|
||||
{(() => {
|
||||
if (pathname.startsWith("/dashboard/folders") && currentFolderType) {
|
||||
const match = navigation.find((it) => it.type === currentFolderType);
|
||||
return match?.name || "Dossiers";
|
||||
}
|
||||
return navigation.find((it) => it.href === pathname)?.name || "Page";
|
||||
})()}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
@ -265,25 +299,39 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
{isMockMode && (
|
||||
<Badge variant="outline" className="bg-green-50 text-green-700 border-green-200">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="bg-green-50 dark:bg-green-800 text-green-700 dark:text-green-200 border-green-200 dark:border-green-700"
|
||||
>
|
||||
<TestTube className="h-4 w-4 mr-1" />
|
||||
Mode Démo
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<Button variant="ghost" size="sm">
|
||||
<Bell className="h-5 w-5" />
|
||||
<Bell className="h-5 w-5 text-gray-900 dark:text-gray-100" />
|
||||
</Button>
|
||||
|
||||
<Button variant="ghost" size="sm">
|
||||
<Settings className="h-5 w-5" />
|
||||
</Button>
|
||||
<Link href="/dashboard/settings">
|
||||
<Button variant="ghost" size="sm" title="Paramètres">
|
||||
<Settings className="h-5 w-5 text-gray-900 dark:text-gray-100" />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
title="Accès à la clé privée"
|
||||
className={`h-9 w-9 flex items-center justify-center ${isPrivateKeyFlash ? "text-red-600" : "text-gray-700 dark:text-gray-300"
|
||||
}`}
|
||||
aria-hidden
|
||||
>
|
||||
<Key className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Page content */}
|
||||
<main className="flex-1 overflow-auto bg-gray-50">
|
||||
<main className="flex-1 overflow-auto bg-gray-50 dark:bg-gray-900">
|
||||
<div className="p-6">{children}</div>
|
||||
</main>
|
||||
</div>
|
||||
@ -291,33 +339,29 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
{/* Modal de confirmation de déconnexion */}
|
||||
{showLogoutConfirm && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
|
||||
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md w-full mx-4">
|
||||
<div className="text-center">
|
||||
<Shield className="h-12 w-12 mx-auto mb-4 text-blue-600" />
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">Déconnexion réussie</h3>
|
||||
<p className="text-gray-600 mb-6">Vous avez été déconnecté de votre espace sécurisé DocV.</p>
|
||||
<Shield className="h-12 w-12 mx-auto mb-4 text-blue-600 dark:text-blue-400" />
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Déconnexion réussie</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-6">
|
||||
Vous avez été déconnecté de votre espace sécurisé DocV.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{/* <Button onClick={() => confirmLogout(false)} className="w-full">
|
||||
<LogOut className="h-4 w-4 mr-2" />
|
||||
Aller à la page de connexion
|
||||
</Button> */}
|
||||
|
||||
<Button onClick={() => confirmLogout(true)} variant="outline" className="w-full">
|
||||
<Button onClick={() => router.push("/")} variant="outline" className="w-full">
|
||||
<Home className="h-4 w-4 mr-2" />
|
||||
Retourner à l'accueil
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-gray-500 mt-4">Vos données restent sécurisées par le chiffrement 4NK</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-4">
|
||||
Vos données restent sécurisées par le chiffrement 4NK
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isConnected && <Iframe iframeUrl={iframeUrl} />}
|
||||
|
||||
{/* Debug info retiré */}
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useState, useEffect, useCallback } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
@ -25,11 +25,35 @@ import {
|
||||
Zap,
|
||||
HardDrive,
|
||||
X,
|
||||
FolderPlus,
|
||||
Brain,
|
||||
XCircle,
|
||||
Info,
|
||||
} from "lucide-react"
|
||||
import MessageBus from "@/lib/4nk/MessageBus"
|
||||
import Link from "next/link"
|
||||
import Chat from "@/components/4nk/Chat"
|
||||
import UserStore from "@/lib/4nk/UserStore"
|
||||
import EventBus from "@/lib/4nk/EventBus"
|
||||
import { iframeUrl } from "../page"
|
||||
import Iframe from "@/components/4nk/Iframe"
|
||||
|
||||
type FolderType = "contrat" | "projet" | "rapport" | "finance" | "rh" | "marketing";
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [isMockMode, setIsMockMode] = useState(false)
|
||||
const [notification, setNotification] = useState<{ type: "success" | "error" | "info"; message: string } | null>(null)
|
||||
|
||||
// 4NK Integration states
|
||||
const [isConnected, setIsConnected] = useState(false)
|
||||
const [showAuthModal, setShowAuthModal] = useState(false)
|
||||
const [processes, setProcesses] = useState<any>(null)
|
||||
const [myProcesses, setMyProcesses] = useState<string[]>([])
|
||||
const [userPairingId, setUserPairingId] = useState<string | null>(null)
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [folderType, setFolderType] = useState<FolderType | null>(null);
|
||||
|
||||
const [stats, setStats] = useState({
|
||||
totalDocuments: 0,
|
||||
totalFolders: 0,
|
||||
@ -245,20 +269,152 @@ export default function DashboardPage() {
|
||||
}
|
||||
}
|
||||
|
||||
// 4NK Integration useEffects
|
||||
useEffect(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const connected = userStore.isConnected();
|
||||
const pairingId = userStore.getUserPairingId();
|
||||
|
||||
console.log('Initialisation 4NK:', { connected, pairingId });
|
||||
|
||||
setIsConnected(connected);
|
||||
setUserPairingId(pairingId);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected) {
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
messageBus.isReady().then(() => {
|
||||
messageBus.getProcesses().then((processes: any) => {
|
||||
setProcesses(processes);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [isConnected, iframeUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && processes !== null) {
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
messageBus.isReady().then(() => {
|
||||
messageBus.getMyProcesses().then((res: string[]) => {
|
||||
setMyProcesses(res);
|
||||
})
|
||||
});
|
||||
}
|
||||
}, [isConnected, processes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && userPairingId === null) {
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
messageBus.isReady().then(() => {
|
||||
messageBus.getUserPairingId().then((userPairingId: string) => {
|
||||
UserStore.getInstance().pair(userPairingId);
|
||||
setUserPairingId(UserStore.getInstance().getUserPairingId());
|
||||
})
|
||||
});
|
||||
}
|
||||
}, [isConnected, userPairingId, processes]);
|
||||
|
||||
const handleOpenModal = (type: FolderType) => {
|
||||
setFolderType(type);
|
||||
setIsModalOpen(true);
|
||||
setMenuOpen(false);
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setIsModalOpen(false);
|
||||
setFolderType(null);
|
||||
};
|
||||
|
||||
// Notification system
|
||||
const showNotification = (type: "success" | "error" | "info", message: string) => {
|
||||
setNotification({ type, message })
|
||||
setTimeout(() => setNotification(null), 3000)
|
||||
}
|
||||
|
||||
// 4NK handlers
|
||||
|
||||
// Debug function pour forcer la récupération du userPairingId
|
||||
const handleForceGetPairingId = useCallback(() => {
|
||||
console.log('Force récupération userPairingId - État actuel:', {
|
||||
isConnected,
|
||||
userPairingId,
|
||||
userStoreConnected: UserStore.getInstance().isConnected(),
|
||||
userStorePairingId: UserStore.getInstance().getUserPairingId()
|
||||
});
|
||||
|
||||
// D'abord essayer de synchroniser depuis UserStore
|
||||
const userStorePairingId = UserStore.getInstance().getUserPairingId();
|
||||
if (userStorePairingId) {
|
||||
console.log('Force - Synchronisation depuis UserStore:', userStorePairingId);
|
||||
setUserPairingId(userStorePairingId);
|
||||
showNotification("success", `UserPairingId synchronisé: ${userStorePairingId.substring(0, 8)}...`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sinon récupérer depuis MessageBus
|
||||
if (isConnected) {
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
messageBus.isReady().then(() => {
|
||||
console.log('Force - MessageBus prêt');
|
||||
messageBus.getUserPairingId().then((retrievedPairingId: string) => {
|
||||
console.log('Force - UserPairingId récupéré:', retrievedPairingId);
|
||||
UserStore.getInstance().pair(retrievedPairingId);
|
||||
setUserPairingId(retrievedPairingId);
|
||||
showNotification("success", `UserPairingId récupéré: ${retrievedPairingId.substring(0, 8)}...`);
|
||||
}).catch((error) => {
|
||||
console.error('Force - Erreur récupération userPairingId:', error);
|
||||
showNotification("error", "Erreur lors de la récupération du userPairingId");
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error('Force - Erreur MessageBus isReady:', error);
|
||||
showNotification("error", "MessageBus non prêt");
|
||||
});
|
||||
} else {
|
||||
showNotification("error", "Vous devez être connecté à 4NK");
|
||||
}
|
||||
}, [isConnected, userPairingId, iframeUrl]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Notification */}
|
||||
{notification && (
|
||||
<div
|
||||
className={`fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg flex items-center space-x-2 ${notification.type === "success"
|
||||
? "bg-green-100 text-green-800 border border-green-200"
|
||||
: notification.type === "error"
|
||||
? "bg-red-100 text-red-800 border border-red-200"
|
||||
: "bg-blue-100 text-blue-800 border border-blue-200"
|
||||
}`}
|
||||
>
|
||||
{notification.type === "success" && <CheckCircle className="h-5 w-5" />}
|
||||
{notification.type === "error" && <XCircle className="h-5 w-5" />}
|
||||
{notification.type === "info" && <Info className="h-5 w-5" />}
|
||||
<span>{notification.message}</span>
|
||||
<Button variant="ghost" size="sm" onClick={() => setNotification(null)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{/* En-tête */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Tableau de bord</h1>
|
||||
<p className="text-gray-600">Vue d'ensemble de votre espace documentaire sécurisé</p>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">My work</h1>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
Vue d'ensemble de votre espace documentaire sécurisé
|
||||
</p>
|
||||
</div>
|
||||
{isMockMode && (
|
||||
<Badge variant="outline" className="bg-green-50 text-green-700 border-green-200">
|
||||
<TestTube className="h-4 w-4 mr-2" />
|
||||
Données de démonstration
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
{/* Debug PairingId */}
|
||||
{!userPairingId && (
|
||||
<Button variant="outline" size="sm" onClick={handleForceGetPairingId}>
|
||||
<Brain className="h-4 w-4 mr-2" />
|
||||
Debug PairingId
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Statistiques principales */}
|
||||
@ -267,179 +423,119 @@ export default function DashboardPage() {
|
||||
{/* Conserver uniquement les autres indicateurs utiles (ex : Jetons utilisés, stockage, etc.) */}
|
||||
</div>
|
||||
|
||||
{/* Messages intégrés */}
|
||||
<div className="mt-6">
|
||||
<Chat heightClass="h-[600px]" />
|
||||
</div>
|
||||
|
||||
{/* Nouveaux indicateurs de stockage */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<Card className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Stockage permanent</CardTitle>
|
||||
<HardDrive className="h-4 w-4 text-blue-600" />
|
||||
<CardTitle className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
Stockage permanent
|
||||
</CardTitle>
|
||||
<HardDrive className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{stats.permanentStorage} Go</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2 mt-2">
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">{stats.permanentStorage} Go</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mt-2">
|
||||
<div
|
||||
className="bg-blue-600 h-2 rounded-full"
|
||||
className="bg-blue-600 dark:bg-blue-400 h-2 rounded-full"
|
||||
style={{ width: `${(stats.permanentStorage / stats.permanentStorageLimit) * 100}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.permanentStorage} Go / {stats.permanentStorageLimit} Go (1 To)
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Stockage temporaire</CardTitle>
|
||||
<Zap className="h-4 w-4 text-orange-600" />
|
||||
<CardTitle className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
Stockage temporaire
|
||||
</CardTitle>
|
||||
<Zap className="h-4 w-4 text-orange-600 dark:text-orange-400" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{stats.temporaryStorage} Go</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2 mt-2">
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">{stats.temporaryStorage} Go</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mt-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${
|
||||
stats.temporaryStorage > 80
|
||||
? "bg-red-600"
|
||||
: stats.temporaryStorage > 60
|
||||
? "bg-orange-600"
|
||||
: "bg-green-600"
|
||||
}`}
|
||||
className={`h-2 rounded-full ${stats.temporaryStorage > 80
|
||||
? "bg-red-600 dark:bg-red-500"
|
||||
: stats.temporaryStorage > 60
|
||||
? "bg-orange-600 dark:bg-orange-500"
|
||||
: "bg-green-600 dark:bg-green-500"
|
||||
}`}
|
||||
style={{ width: `${(stats.temporaryStorage / stats.temporaryStorageLimit) * 100}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.temporaryStorage} Go / {stats.temporaryStorageLimit} Go ce mois
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Nouveaux dossiers</CardTitle>
|
||||
<Plus className="h-4 w-4 text-green-600" />
|
||||
<CardTitle className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
Nouveaux dossiers
|
||||
</CardTitle>
|
||||
<Plus className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{stats.newFoldersThisMonth}</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2 mt-2">
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">{stats.newFoldersThisMonth}</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mt-2">
|
||||
<div
|
||||
className="bg-green-600 h-2 rounded-full"
|
||||
className="bg-green-600 dark:bg-green-400 h-2 rounded-full"
|
||||
style={{ width: `${(stats.newFoldersThisMonth / stats.newFoldersLimit) * 100}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{stats.newFoldersThisMonth} / {stats.newFoldersLimit} ce mois
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Documents récents */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span className="flex items-center">
|
||||
<FileText className="h-5 w-5 mr-2" />
|
||||
Documents récents
|
||||
</span>
|
||||
<Link href="/dashboard/documents">
|
||||
<Button variant="ghost" size="sm">
|
||||
Voir tout
|
||||
</Button>
|
||||
</Link>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{recentDocuments.map((doc) => (
|
||||
<div key={doc.id} className="flex items-center space-x-4 p-3 rounded-lg hover:bg-gray-50">
|
||||
<div className="text-2xl">{getFileIcon(doc.type)}</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">{doc.name}</p>
|
||||
<div className="flex items-center space-x-2 text-xs text-gray-500">
|
||||
<span>{doc.folder}</span>
|
||||
<span>•</span>
|
||||
<span>{doc.size}</span>
|
||||
<span>•</span>
|
||||
<span>{doc.modifiedAt}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={getStatusColor(doc.status)}>{doc.status}</Badge>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Activité récente */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<Activity className="h-5 w-5 mr-2" />
|
||||
Activité récente
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{recentActivity.map((activity) => (
|
||||
<div key={activity.id} className="flex items-start space-x-3">
|
||||
<div className={`p-2 rounded-full bg-gray-100 ${activity.color}`}>
|
||||
<activity.icon className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm text-gray-900">
|
||||
<span className="font-medium">{activity.user}</span> {activity.action}{" "}
|
||||
<span className="font-medium">{activity.target}</span>
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 flex items-center">
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
{activity.time}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Sécurité */}
|
||||
<Card>
|
||||
<Card className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<Shield className="h-5 w-5 mr-2 text-green-600" />
|
||||
<CardTitle className="flex items-center text-gray-900 dark:text-gray-100">
|
||||
<Shield className="h-5 w-5 mr-2 text-green-600 dark:text-green-400" />
|
||||
Statut de sécurité
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center space-x-4 p-4 bg-green-50 rounded-lg">
|
||||
<CheckCircle className="h-8 w-8 text-green-600" />
|
||||
<div className="flex items-center space-x-4 p-4 bg-green-50 dark:bg-green-900 rounded-lg">
|
||||
<CheckCircle className="h-8 w-8 text-green-600 dark:text-green-400" />
|
||||
<div>
|
||||
<h4 className="font-medium text-green-900">Sécurité optimale</h4>
|
||||
<p className="text-sm text-green-700">
|
||||
<h4 className="font-medium text-green-900 dark:text-green-300">Sécurité optimale</h4>
|
||||
<p className="text-sm text-green-700 dark:text-green-200">
|
||||
Tous vos documents sont chiffrés et sécurisés par la technologie 4NK
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
|
||||
<div className="text-center p-3">
|
||||
<Shield className="h-6 w-6 mx-auto text-green-600 mb-2" />
|
||||
<p className="text-sm font-medium">Chiffrement bout en bout</p>
|
||||
<Shield className="h-6 w-6 mx-auto text-green-600 dark:text-green-400 mb-2" />
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">Chiffrement bout en bout</p>
|
||||
</div>
|
||||
<div className="text-center p-3">
|
||||
<CheckCircle className="h-6 w-6 mx-auto text-green-600 mb-2" />
|
||||
<p className="text-sm font-medium">Authentification 4NK</p>
|
||||
<CheckCircle className="h-6 w-6 mx-auto text-green-600 dark:text-green-400 mb-2" />
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">Authentification 4NK</p>
|
||||
</div>
|
||||
<div className="text-center p-3">
|
||||
<Activity className="h-6 w-6 mx-auto text-green-600 mb-2" />
|
||||
<p className="text-sm font-medium">Audit complet</p>
|
||||
<Activity className="h-6 w-6 mx-auto text-green-600 dark:text-green-400 mb-2" />
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">Audit complet</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 4NK Iframe - only show when connected */}
|
||||
{isConnected && <Iframe iframeUrl={iframeUrl} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -51,10 +51,11 @@ export default function SettingsPage() {
|
||||
bio: "Utilisateur de démonstration pour DocV",
|
||||
},
|
||||
security: {
|
||||
twoFactorEnabled: true,
|
||||
sessionTimeout: "30",
|
||||
passwordLastChanged: new Date("2024-01-01"),
|
||||
activeDevices: 1, // Simuler un seul device
|
||||
devices: [
|
||||
{ id: "current", label: "Appareil actuel", addedAt: new Date().toISOString(), ratio: 100 },
|
||||
],
|
||||
},
|
||||
notifications: {
|
||||
emailNotifications: true,
|
||||
@ -85,21 +86,36 @@ export default function SettingsPage() {
|
||||
},
|
||||
})
|
||||
|
||||
const [showApiKey, setShowApiKey] = useState(false)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [notification, setNotification] = useState<{ type: "success" | "error" | "info"; message: string } | null>(null)
|
||||
const [showPairingWords, setShowPairingWords] = useState(false)
|
||||
const [isSyncing, setIsSyncing] = useState(false)
|
||||
const [syncProgress, setSyncProgress] = useState(0)
|
||||
const [isImporting, setIsImporting] = useState(false)
|
||||
const [isDarkTheme, setIsDarkTheme] = useState(false)
|
||||
const [newDeviceLabel, setNewDeviceLabel] = useState("")
|
||||
const [newDeviceRatio, setNewDeviceRatio] = useState(50)
|
||||
|
||||
// Vérifier si un seul device est connecté au chargement
|
||||
useEffect(() => {
|
||||
if (settings.security.activeDevices === 1) {
|
||||
// Attendre un peu avant d'afficher la modal pour laisser le temps à la page de se charger
|
||||
const timer = setTimeout(() => {
|
||||
setShowAddDeviceModal(true)
|
||||
}, 2000)
|
||||
return () => clearTimeout(timer)
|
||||
if (typeof window !== "undefined") {
|
||||
const saved = localStorage.getItem("theme")
|
||||
const dark = saved ? saved === "dark" : window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
setIsDarkTheme(dark)
|
||||
document.documentElement.classList.toggle("dark", dark)
|
||||
}
|
||||
}, [settings.security.activeDevices])
|
||||
}, [])
|
||||
|
||||
const toggleTheme = (checked: boolean) => {
|
||||
setIsDarkTheme(checked)
|
||||
if (typeof document !== "undefined") {
|
||||
document.documentElement.classList.toggle("dark", checked)
|
||||
}
|
||||
if (typeof localStorage !== "undefined") {
|
||||
localStorage.setItem("theme", checked ? "dark" : "light")
|
||||
}
|
||||
}
|
||||
|
||||
// Retrait de l'ouverture automatique de la modale d'appareil
|
||||
|
||||
const showNotification = (type: "success" | "error" | "info", message: string) => {
|
||||
setNotification({ type, message })
|
||||
@ -108,12 +124,9 @@ export default function SettingsPage() {
|
||||
|
||||
const tabs = [
|
||||
{ id: "profile", name: "Profil", icon: User },
|
||||
{ id: "security", name: "Sécurité", icon: Shield },
|
||||
{ id: "notifications", name: "Notifications", icon: Bell },
|
||||
{ id: "appearance", name: "Apparence", icon: Palette },
|
||||
{ id: "privacy", name: "Confidentialité", icon: Lock },
|
||||
{ id: "storage", name: "Stockage", icon: Database },
|
||||
{ id: "api", name: "API", icon: Key },
|
||||
{ id: "devices", name: "Appareils", icon: Smartphone },
|
||||
{ id: "import", name: "Import", icon: Upload },
|
||||
{ id: "sync", name: "Synchroniser", icon: RefreshCw },
|
||||
]
|
||||
|
||||
const handleSave = async () => {
|
||||
@ -132,23 +145,9 @@ export default function SettingsPage() {
|
||||
setShowExportConfirmation(false)
|
||||
showNotification("info", "Export des données en cours...")
|
||||
|
||||
// Simuler l'export de toutes les données IndexedDB
|
||||
setTimeout(() => {
|
||||
// Créer un objet simulant les données exportées
|
||||
const exportData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
userData: settings,
|
||||
documents: "Données des documents chiffrées",
|
||||
folders: "Données des dossiers chiffrées",
|
||||
privateKey: "PRIVATE_KEY_ENCRYPTED_DATA",
|
||||
certificates: "Certificats blockchain",
|
||||
chatHistory: "Historique des conversations",
|
||||
preferences: "Préférences utilisateur",
|
||||
warning: "⚠️ Ce fichier contient votre clé privée. Gardez-le en sécurité !",
|
||||
}
|
||||
|
||||
// Simuler le téléchargement
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: "application/json" })
|
||||
try {
|
||||
const data = await exportIndexedDB()
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement("a")
|
||||
a.href = url
|
||||
@ -157,15 +156,134 @@ export default function SettingsPage() {
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
showNotification("success", "Export terminé. Fichier téléchargé avec succès.")
|
||||
}, 3000)
|
||||
} catch (e: any) {
|
||||
showNotification("error", e?.message || "Échec de l'export IndexedDB")
|
||||
}
|
||||
}
|
||||
|
||||
const generateApiKey = () => {
|
||||
return "docv_" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
|
||||
// Exporter toutes les bases IndexedDB (si supporté)
|
||||
async function exportIndexedDB() {
|
||||
const result: any = { timestamp: new Date().toISOString(), databases: [] as any[] }
|
||||
const dbList = (indexedDB as any).databases ? await (indexedDB as any).databases() : []
|
||||
const dbNames: string[] = dbList?.map((d: any) => d.name).filter(Boolean) || []
|
||||
// Fallback: si l'API databases() n'est pas dispo, utiliser une liste vide (app ne définit pas de DB explicites)
|
||||
for (const name of dbNames) {
|
||||
if (!name) continue
|
||||
const dbDump: any = { name, version: 1, stores: {} as any }
|
||||
const db: IDBDatabase = await new Promise((resolve, reject) => {
|
||||
const open = indexedDB.open(name)
|
||||
open.onsuccess = () => resolve(open.result)
|
||||
open.onerror = () => reject(open.error)
|
||||
})
|
||||
dbDump.version = db.version
|
||||
const storeNames = Array.from(db.objectStoreNames)
|
||||
for (const storeName of storeNames) {
|
||||
dbDump.stores[storeName] = []
|
||||
const tx = db.transaction(storeName, "readonly")
|
||||
const store = tx.objectStore(storeName)
|
||||
const all: any[] = await new Promise((resolve, reject) => {
|
||||
const req = store.getAll()
|
||||
req.onsuccess = () => resolve(req.result)
|
||||
req.onerror = () => reject(req.error)
|
||||
})
|
||||
dbDump.stores[storeName] = all
|
||||
}
|
||||
db.close()
|
||||
result.databases.push(dbDump)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function importIndexedDBFromFile(file: File) {
|
||||
setIsImporting(true)
|
||||
try {
|
||||
const text = await file.text()
|
||||
const data = JSON.parse(text)
|
||||
if (!data?.databases) throw new Error("Fichier d'import invalide")
|
||||
for (const dbDump of data.databases) {
|
||||
const name = dbDump.name as string
|
||||
const version = dbDump.version as number
|
||||
const db: IDBDatabase = await new Promise((resolve, reject) => {
|
||||
const open = indexedDB.open(name, version)
|
||||
open.onupgradeneeded = () => {
|
||||
const dbu = open.result
|
||||
for (const storeName of Object.keys(dbDump.stores || {})) {
|
||||
if (!dbu.objectStoreNames.contains(storeName)) {
|
||||
dbu.createObjectStore(storeName, { autoIncrement: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
open.onsuccess = () => resolve(open.result)
|
||||
open.onerror = () => reject(open.error)
|
||||
})
|
||||
for (const [storeName, records] of Object.entries<any>(dbDump.stores || {})) {
|
||||
const tx = db.transaction(storeName, "readwrite")
|
||||
const store = tx.objectStore(storeName)
|
||||
await new Promise((resolve, reject) => {
|
||||
const clearReq = store.clear()
|
||||
clearReq.onsuccess = () => resolve(true)
|
||||
clearReq.onerror = () => reject(clearReq.error)
|
||||
})
|
||||
for (const rec of records) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const req = store.add(rec)
|
||||
req.onsuccess = () => resolve(true)
|
||||
req.onerror = () => reject(req.error)
|
||||
})
|
||||
}
|
||||
}
|
||||
db.close()
|
||||
}
|
||||
showNotification("success", "Import terminé avec succès")
|
||||
} catch (e: any) {
|
||||
showNotification("error", e?.message || "Échec de l'import IndexedDB")
|
||||
} finally {
|
||||
setIsImporting(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function synchronizeIndexedDBPreserveKeys() {
|
||||
setIsSyncing(true)
|
||||
setSyncProgress(0)
|
||||
try {
|
||||
const dbList = (indexedDB as any).databases ? await (indexedDB as any).databases() : []
|
||||
const dbNames: string[] = dbList?.map((d: any) => d.name).filter(Boolean) || []
|
||||
let processed = 0
|
||||
for (const name of dbNames) {
|
||||
const db: IDBDatabase = await new Promise((resolve, reject) => {
|
||||
const open = indexedDB.open(name)
|
||||
open.onsuccess = () => resolve(open.result)
|
||||
open.onerror = () => reject(open.error)
|
||||
})
|
||||
const storeNames = Array.from(db.objectStoreNames)
|
||||
for (const storeName of storeNames) {
|
||||
const shouldPreserve = /key/i.test(storeName)
|
||||
if (shouldPreserve) continue
|
||||
const tx = db.transaction(storeName, "readwrite")
|
||||
const store = tx.objectStore(storeName)
|
||||
await new Promise((resolve, reject) => {
|
||||
const clearReq = store.clear()
|
||||
clearReq.onsuccess = () => resolve(true)
|
||||
clearReq.onerror = () => reject(clearReq.error)
|
||||
})
|
||||
}
|
||||
db.close()
|
||||
processed += 1
|
||||
setSyncProgress(Math.round((processed / Math.max(1, dbNames.length)) * 100))
|
||||
}
|
||||
// Barre de progression finale
|
||||
setSyncProgress(100)
|
||||
showNotification("success", "Synchronisation lancée: données (hors clés) vidées")
|
||||
} catch (e: any) {
|
||||
showNotification("error", e?.message || "Échec de la synchronisation")
|
||||
} finally {
|
||||
setTimeout(() => setIsSyncing(false), 400)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const generatePairingWords = () => {
|
||||
const words = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel"]
|
||||
return Array.from({ length: 4 }, () => words[Math.floor(Math.random() * words.length)])
|
||||
@ -314,107 +432,74 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderSecurityTab = () => (
|
||||
const renderDevicesTab = () => (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Authentification</CardTitle>
|
||||
<CardTitle>Appareils</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium">Authentification à deux facteurs</h4>
|
||||
<p className="text-sm text-gray-500">Sécurisez votre compte avec 4NK (obligatoire)</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch checked={true} disabled={true} className="opacity-50" />
|
||||
<Badge className="bg-red-100 text-red-800 border-red-200">Obligatoire</Badge>
|
||||
<h4 className="font-medium">Gestion des appareils</h4>
|
||||
<p className="text-sm text-gray-500">Définissez un label et un ratio de signature par appareil</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={() => { setNewDeviceLabel(""); setNewDeviceRatio(50); setShowAddDeviceModal(true) }}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Ajouter un appareil
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="sessionTimeout">Délai d'expiration de session (minutes)</Label>
|
||||
<Select
|
||||
value={settings.security.sessionTimeout}
|
||||
onValueChange={(value) =>
|
||||
setSettings({
|
||||
...settings,
|
||||
security: { ...settings.security, sessionTimeout: value },
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="15">15 minutes</SelectItem>
|
||||
<SelectItem value="30">30 minutes</SelectItem>
|
||||
<SelectItem value="60">1 heure</SelectItem>
|
||||
<SelectItem value="120">2 heures</SelectItem>
|
||||
<SelectItem value="480">8 heures</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="bg-green-50 p-4 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="h-5 w-5 text-green-600" />
|
||||
<span className="font-medium text-green-900">Sécurité 4NK active</span>
|
||||
</div>
|
||||
<p className="text-sm text-green-700 mt-1">Votre compte est protégé par le chiffrement bout en bout 4NK</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Appareils connectés</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Smartphone className="h-5 w-5 text-gray-600" />
|
||||
<div>
|
||||
<p className="font-medium">Navigateur actuel</p>
|
||||
<p className="text-sm text-gray-500">Chrome sur Windows • Maintenant</p>
|
||||
{settings.security.devices?.map((dev: any, idx: number) => (
|
||||
<div key={dev.id || idx} className="p-3 rounded-lg bg-gray-50 border flex items-center gap-3">
|
||||
<Input
|
||||
value={dev.label}
|
||||
onChange={(e) => setSettings(prev => ({
|
||||
...prev,
|
||||
security: {
|
||||
...prev.security,
|
||||
devices: prev.security.devices.map((d: any) => d === dev ? { ...d, label: e.target.value } : d)
|
||||
}
|
||||
}))}
|
||||
className="max-w-xs"
|
||||
/>
|
||||
{dev.id === "current" && (
|
||||
<Badge className="bg-green-100 text-green-800 border-green-200">Actuel</Badge>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<Label>Ratio</Label>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={100}
|
||||
value={dev.ratio}
|
||||
onChange={(e) => setSettings(prev => ({
|
||||
...prev,
|
||||
security: {
|
||||
...prev.security,
|
||||
devices: prev.security.devices.map((d: any) => d === dev ? { ...d, ratio: Number(e.target.value) } : d)
|
||||
}
|
||||
}))}
|
||||
/>
|
||||
<span className="text-sm text-gray-600 w-10">{dev.ratio}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className="bg-green-100 text-green-800">Actuel</Badge>
|
||||
</div>
|
||||
|
||||
{settings.security.activeDevices > 1 && (
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Smartphone className="h-5 w-5 text-gray-600" />
|
||||
<div>
|
||||
<p className="font-medium">iPhone</p>
|
||||
<p className="text-sm text-gray-500">Safari • Il y a 2 heures</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline">Connecté</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{settings.security.activeDevices === 1 && (
|
||||
<div className="bg-orange-50 p-4 rounded-lg border border-orange-200">
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<AlertTriangle className="h-5 w-5 text-orange-600" />
|
||||
<span className="font-medium text-orange-900">Un seul appareil connecté</span>
|
||||
</div>
|
||||
<p className="text-sm text-orange-800 mb-3">
|
||||
Pour votre sécurité, nous recommandons d'ajouter un second appareil de confiance.
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowAddDeviceModal(true)}
|
||||
className="bg-orange-100 text-orange-800 border-orange-300 hover:bg-orange-200"
|
||||
className="ml-auto"
|
||||
disabled={dev.id === "current"}
|
||||
onClick={() => setSettings(prev => ({
|
||||
...prev,
|
||||
security: { ...prev.security, devices: prev.security.devices.filter((d: any) => d !== dev) }
|
||||
}))}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Ajouter un appareil
|
||||
<Trash2 className="h-4 w-4 mr-2" /> Retirer
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{(!settings.security.devices || settings.security.devices.length === 0) && (
|
||||
<p className="text-sm text-gray-500">Aucun appareil pour le moment.</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
@ -750,194 +835,74 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderStorageTab = () => (
|
||||
const renderImportTab = () => (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Utilisation du stockage</CardTitle>
|
||||
<CardTitle>Import</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium">Espace utilisé</span>
|
||||
<span className="text-sm text-gray-600">
|
||||
{settings.storage.used} GB / {settings.storage.total} GB
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-3">
|
||||
<div className="bg-blue-600 h-3 rounded-full" style={{ width: `${settings.storage.used}%` }}></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="text-center p-4 bg-blue-50 rounded-lg">
|
||||
<HardDrive className="h-8 w-8 mx-auto text-blue-600 mb-2" />
|
||||
<p className="font-medium">Documents</p>
|
||||
<p className="text-sm text-gray-600">45.2 GB</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-green-50 rounded-lg">
|
||||
<Activity className="h-8 w-8 mx-auto text-green-600 mb-2" />
|
||||
<p className="font-medium">Sauvegardes</p>
|
||||
<p className="text-sm text-gray-600">15.8 GB</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-purple-50 rounded-lg">
|
||||
<Database className="h-8 w-8 mx-auto text-purple-600 mb-2" />
|
||||
<p className="font-medium">Métadonnées</p>
|
||||
<p className="text-sm text-gray-600">6.3 GB</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium">Sauvegarde automatique</h4>
|
||||
<p className="text-sm text-gray-500">Sauvegarder automatiquement vos données</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.storage.autoBackup}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings({
|
||||
...settings,
|
||||
storage: { ...settings.storage, autoBackup: checked },
|
||||
})
|
||||
}
|
||||
<Label htmlFor="importFile">Fichier d'import (.json)</Label>
|
||||
<input
|
||||
id="importFile"
|
||||
type="file"
|
||||
accept="application/json"
|
||||
onChange={(e) => {
|
||||
const f = e.target.files?.[0]
|
||||
if (f) importIndexedDBFromFile(f)
|
||||
}}
|
||||
disabled={isImporting}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="retentionPeriod">Période de rétention (jours)</Label>
|
||||
<Select
|
||||
value={settings.storage.retentionPeriod}
|
||||
onValueChange={(value) =>
|
||||
setSettings({
|
||||
...settings,
|
||||
storage: { ...settings.storage, retentionPeriod: value },
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="30">30 jours</SelectItem>
|
||||
<SelectItem value="90">90 jours</SelectItem>
|
||||
<SelectItem value="180">180 jours</SelectItem>
|
||||
<SelectItem value="365">1 an</SelectItem>
|
||||
<SelectItem value="unlimited">Illimitée</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" onClick={handleExportData}>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Exporter les données
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
Nettoyer le cache
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">Le contenu remplacera les données locales des stores correspondants.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderApiTab = () => (
|
||||
const renderSyncTab = () => (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Clés API</CardTitle>
|
||||
<CardTitle>Synchroniser</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Key className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||
<div>
|
||||
<h4 className="font-medium text-blue-900">API DocV</h4>
|
||||
<p className="text-sm text-blue-700 mt-1">Utilisez l'API pour intégrer DocV avec vos applications</p>
|
||||
<p className="text-sm text-gray-600">Vide toutes les données IndexedDB en conservant les stores contenant « key ».</p>
|
||||
<Button onClick={synchronizeIndexedDBPreserveKeys} disabled={isSyncing}>
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
Lancer la synchronisation
|
||||
</Button>
|
||||
{isSyncing && (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-sm text-gray-600">Synchronisation en cours...</span>
|
||||
<span className="text-sm text-gray-600">{syncProgress}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-3">
|
||||
<div className="bg-blue-600 h-3 rounded-full" style={{ width: `${syncProgress}%` }}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="apiKey">Clé API principale</Label>
|
||||
<div className="flex space-x-2 mt-1">
|
||||
<Input
|
||||
id="apiKey"
|
||||
type={showApiKey ? "text" : "password"}
|
||||
value={generateApiKey()}
|
||||
readOnly
|
||||
className="font-mono"
|
||||
/>
|
||||
<Button variant="outline" size="sm" onClick={() => setShowApiKey(!showApiKey)}>
|
||||
{showApiKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline">
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
Régénérer la clé
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
<ExternalLink className="h-4 w-4 mr-2" />
|
||||
Documentation API
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="bg-yellow-50 p-4 rounded-lg">
|
||||
<div className="flex items-start space-x-3">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-600 mt-0.5" />
|
||||
<div>
|
||||
<h4 className="font-medium text-yellow-900">Sécurité</h4>
|
||||
<p className="text-sm text-yellow-700 mt-1">
|
||||
Ne partagez jamais votre clé API. Régénérez-la si elle est compromise.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Webhooks</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-center py-8">
|
||||
<Globe className="h-12 w-12 mx-auto text-gray-400 mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Aucun webhook configuré</h3>
|
||||
<p className="text-gray-600 mb-4">Configurez des webhooks pour recevoir des notifications en temps réel</p>
|
||||
<Button>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Ajouter un webhook
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
|
||||
const renderTabContent = () => {
|
||||
switch (activeTab) {
|
||||
case "profile":
|
||||
return renderProfileTab()
|
||||
case "security":
|
||||
return renderSecurityTab()
|
||||
case "notifications":
|
||||
return renderNotificationsTab()
|
||||
case "appearance":
|
||||
return renderAppearanceTab()
|
||||
case "privacy":
|
||||
return renderPrivacyTab()
|
||||
case "storage":
|
||||
return renderStorageTab()
|
||||
case "api":
|
||||
return renderApiTab()
|
||||
case "devices":
|
||||
return renderDevicesTab()
|
||||
// Onglets Notifications/Appearance/Privacy retirés
|
||||
case "import":
|
||||
return renderImportTab()
|
||||
case "sync":
|
||||
return renderSyncTab()
|
||||
|
||||
default:
|
||||
return renderProfileTab()
|
||||
}
|
||||
@ -972,10 +937,20 @@ export default function SettingsPage() {
|
||||
<h1 className="text-2xl font-bold text-gray-900">Paramètres</h1>
|
||||
<p className="text-gray-600 mt-1">Gérez vos préférences et paramètres de compte</p>
|
||||
</div>
|
||||
<Button onClick={handleSave} disabled={isSaving}>
|
||||
{isSaving ? <RefreshCw className="h-4 w-4 mr-2 animate-spin" /> : <Save className="h-4 w-4 mr-2" />}
|
||||
{isSaving ? "Sauvegarde..." : "Sauvegarder"}
|
||||
</Button>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-600">Sombre</span>
|
||||
<Switch checked={isDarkTheme} onCheckedChange={toggleTheme} />
|
||||
</div>
|
||||
<Button variant="outline" onClick={handleExportData}>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Export
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={isSaving}>
|
||||
{isSaving ? <RefreshCw className="h-4 w-4 mr-2 animate-spin" /> : <Save className="h-4 w-4 mr-2" />}
|
||||
{isSaving ? "Sauvegarde..." : "Sauvegarder"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
import { useRouter } from "next/navigation"
|
||||
import frenchWords from "bip39/src/wordlists/french.json"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@ -10,6 +12,7 @@ import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { sendInviteEmailAction } from "@/app/actions/users"
|
||||
import {
|
||||
Users,
|
||||
UserPlus,
|
||||
@ -31,6 +34,8 @@ import {
|
||||
UserCheck,
|
||||
} from "lucide-react"
|
||||
|
||||
const INVITE_WORD_LIST: string[] = frenchWords as unknown as string[]
|
||||
|
||||
interface User {
|
||||
id: string
|
||||
name: string
|
||||
@ -59,7 +64,7 @@ interface User {
|
||||
}
|
||||
|
||||
interface ActionModal {
|
||||
type: "invite" | "message" | "roles" | "deactivate" | "delete" | null
|
||||
type: "invite" | "add" | "message" | "roles" | "deactivate" | "delete" | null
|
||||
user: User | null
|
||||
users: User[]
|
||||
}
|
||||
@ -78,12 +83,23 @@ export default function UsersPage() {
|
||||
const [inviteRole, setInviteRole] = useState("viewer")
|
||||
const [inviteDepartment, setInviteDepartment] = useState("")
|
||||
const [inviteMessage, setInviteMessage] = useState("")
|
||||
const [inviteLink, setInviteLink] = useState("")
|
||||
const [inviteWords, setInviteWords] = useState<string[]>([])
|
||||
const [inviteCode, setInviteCode] = useState("")
|
||||
const [inviteResourceTitle, setInviteResourceTitle] = useState("")
|
||||
const [inviteSendTo, setInviteSendTo] = useState("")
|
||||
const [messageContent, setMessageContent] = useState("")
|
||||
const [messageSubject, setMessageSubject] = useState("")
|
||||
const [newUserRole, setNewUserRole] = useState("")
|
||||
const [newSpaceRole, setNewSpaceRole] = useState("")
|
||||
const [deactivateReason, setDeactivateReason] = useState("")
|
||||
|
||||
// Add user modal states
|
||||
const [addName, setAddName] = useState("")
|
||||
const [addEmail, setAddEmail] = useState("")
|
||||
const [addRole, setAddRole] = useState("viewer")
|
||||
const [addDepartment, setAddDepartment] = useState("")
|
||||
|
||||
const [users, setUsers] = useState<User[]>([
|
||||
{
|
||||
id: "1",
|
||||
@ -209,6 +225,9 @@ export default function UsersPage() {
|
||||
admins: users.filter((u) => u.role === "admin").length,
|
||||
}
|
||||
|
||||
// Invite link and QR handling
|
||||
// Lien d'invitation généré dynamiquement
|
||||
|
||||
// Notification system
|
||||
const showNotification = (type: "success" | "error" | "info", message: string) => {
|
||||
setNotification({ type, message })
|
||||
@ -221,9 +240,35 @@ export default function UsersPage() {
|
||||
setInviteRole("viewer")
|
||||
setInviteDepartment("")
|
||||
setInviteMessage("")
|
||||
setInviteResourceTitle("")
|
||||
const token = uuidv4()
|
||||
const origin = typeof window !== "undefined" ? window.location.origin : ""
|
||||
const link = `${origin}/login?invite=${token}`
|
||||
setInviteLink(link)
|
||||
setInviteSendTo("")
|
||||
|
||||
// Générer 4 mots uniques + 1 code de 6 caractères (liste interne FR)
|
||||
const indices = new Set<number>()
|
||||
while (indices.size < 4) {
|
||||
indices.add(Math.floor(Math.random() * INVITE_WORD_LIST.length))
|
||||
}
|
||||
const words = Array.from(indices).map((i) => INVITE_WORD_LIST[i])
|
||||
setInviteWords(words)
|
||||
const charset = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||||
let code = ""
|
||||
for (let i = 0; i < 6; i++) code += charset[Math.floor(Math.random() * charset.length)]
|
||||
setInviteCode(code)
|
||||
setActionModal({ type: "invite", user: null, users: [] })
|
||||
}
|
||||
|
||||
const handleAddUser = () => {
|
||||
setAddName("")
|
||||
setAddEmail("")
|
||||
setAddRole("viewer")
|
||||
setAddDepartment("")
|
||||
setActionModal({ type: "add", user: null, users: [] })
|
||||
}
|
||||
|
||||
const handleMessageUser = (user: User) => {
|
||||
setMessageSubject("")
|
||||
setMessageContent("")
|
||||
@ -270,7 +315,7 @@ export default function UsersPage() {
|
||||
}
|
||||
|
||||
// Modal actions
|
||||
const confirmInvite = () => {
|
||||
const confirmInvite = async () => {
|
||||
const newUser: User = {
|
||||
id: (users.length + 1).toString(),
|
||||
name: inviteEmail.split("@")[0],
|
||||
@ -293,7 +338,26 @@ export default function UsersPage() {
|
||||
}
|
||||
|
||||
setUsers((prev) => [...prev, newUser])
|
||||
showNotification("success", `Invitation envoyée à ${inviteEmail}`)
|
||||
const recipient = (inviteSendTo || inviteEmail || "").trim()
|
||||
if (recipient) {
|
||||
const res = await sendInviteEmailAction({
|
||||
email: recipient,
|
||||
role: inviteRole,
|
||||
words: inviteWords,
|
||||
code: inviteCode,
|
||||
resourceTitle: inviteResourceTitle,
|
||||
link: inviteLink,
|
||||
})
|
||||
if (res.success) {
|
||||
showNotification("success", `Invitation envoyée à ${recipient}`)
|
||||
} else {
|
||||
showNotification("error", res.error || "Erreur lors de l'envoi de l'invitation")
|
||||
}
|
||||
} else {
|
||||
showNotification("info", "Invitation générée. Partagez les mots et le code ou le lien.")
|
||||
}
|
||||
const context = inviteResourceTitle ? ` pour "${inviteResourceTitle}"` : ""
|
||||
showNotification("info", `Invitation${context}: ${inviteWords.join(" ")} + ${inviteCode}`)
|
||||
setActionModal({ type: null, user: null, users: [] })
|
||||
}
|
||||
|
||||
@ -401,9 +465,7 @@ export default function UsersPage() {
|
||||
}
|
||||
|
||||
const filteredUsers = users.filter((user) => {
|
||||
const matchesSearch =
|
||||
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
const matchesSearch = true
|
||||
const matchesRole = roleFilter === "all" || user.role === roleFilter
|
||||
const matchesStatus = statusFilter === "all" || user.status === statusFilter
|
||||
return matchesSearch && matchesRole && matchesStatus
|
||||
@ -502,10 +564,16 @@ export default function UsersPage() {
|
||||
<h1 className="text-2xl font-bold text-gray-900">Gestion des rôles - Profils utilisateurs</h1>
|
||||
<p className="text-gray-600 mt-1">Gérez les utilisateurs, leurs rôles et leurs permissions dans l'espace</p>
|
||||
</div>
|
||||
<Button onClick={handleInviteUser}>
|
||||
<UserPlus className="h-4 w-4 mr-2" />
|
||||
Inviter un utilisateur
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={handleAddUser}>
|
||||
<UserPlus className="h-4 w-4 mr-2" />
|
||||
Ajouter un utilisateur
|
||||
</Button>
|
||||
<Button onClick={handleInviteUser}>
|
||||
<Mail className="h-4 w-4 mr-2" />
|
||||
Inviter un utilisateur
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
@ -556,21 +624,11 @@ export default function UsersPage() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Filters and Search */}
|
||||
{/* Filtres (recherche supprimée) */}
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||
<Input
|
||||
placeholder="Rechercher par nom ou email..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Select value={roleFilter} onValueChange={setRoleFilter}>
|
||||
<SelectTrigger className="w-full sm:w-48">
|
||||
<SelectValue placeholder="Filtrer par rôle" />
|
||||
@ -746,7 +804,7 @@ export default function UsersPage() {
|
||||
<div className="text-center py-8">
|
||||
<Users className="h-12 w-12 mx-auto text-gray-400 mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Aucun utilisateur trouvé</h3>
|
||||
<p className="text-gray-600">Essayez de modifier vos critères de recherche</p>
|
||||
<p className="text-gray-600">Ajustez les filtres de rôle ou de statut</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
@ -756,6 +814,58 @@ export default function UsersPage() {
|
||||
{actionModal.type && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||
<div className="bg-white rounded-lg p-6 w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto">
|
||||
{/* Add User Modal */}
|
||||
{actionModal.type === "add" && (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold">Ajouter un utilisateur</h3>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setActionModal({ type: null, user: null, users: [] })}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="addName">Nom</Label>
|
||||
<Input id="addName" value={addName} onChange={(e) => setAddName(e.target.value)} placeholder="Nom" />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="addEmail">Email</Label>
|
||||
<Input id="addEmail" type="email" value={addEmail} onChange={(e) => setAddEmail(e.target.value)} placeholder="utilisateur@exemple.com" />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="addRole">Rôle</Label>
|
||||
<Select value={addRole} onValueChange={setAddRole}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Sélectionner un rôle" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="viewer">Lecteur</SelectItem>
|
||||
<SelectItem value="editor">Éditeur</SelectItem>
|
||||
<SelectItem value="admin">Administrateur</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="addDepartment">Département</Label>
|
||||
<Input id="addDepartment" value={addDepartment} onChange={(e) => setAddDepartment(e.target.value)} placeholder="ex: Juridique" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<Button variant="outline" onClick={() => setActionModal({ type: null, user: null, users: [] })}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button onClick={confirmAddUser} disabled={!addEmail.trim()}>
|
||||
<UserPlus className="h-4 w-4 mr-2" />
|
||||
Ajouter
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Invite Modal */}
|
||||
{actionModal.type === "invite" && (
|
||||
<>
|
||||
@ -771,7 +881,7 @@ export default function UsersPage() {
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="inviteEmail">Email</Label>
|
||||
<Label htmlFor="inviteEmail">Email (optionnel)</Label>
|
||||
<Input
|
||||
id="inviteEmail"
|
||||
type="email"
|
||||
@ -793,6 +903,75 @@ export default function UsersPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="inviteResourceTitle">Titre du document/dossier (optionnel)</Label>
|
||||
<Input
|
||||
id="inviteResourceTitle"
|
||||
value={inviteResourceTitle}
|
||||
onChange={(e) => setInviteResourceTitle(e.target.value)}
|
||||
placeholder="ex: Contrat ACME / Dossier Finance"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Passphrase d'invitation</Label>
|
||||
<div className="flex flex-wrap gap-2 mt-1">
|
||||
{inviteWords.map((w, i) => (
|
||||
<span key={i} className="px-2 py-1 rounded bg-gray-100 text-gray-800 text-sm">
|
||||
{w}
|
||||
</span>
|
||||
))}
|
||||
{inviteCode && (
|
||||
<span className="px-2 py-1 rounded bg-blue-100 text-blue-800 text-sm">{inviteCode}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<Input readOnly value={`${inviteWords.join(" ")} ${inviteCode}`.trim()} />
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const text = `${inviteWords.join(" ")} ${inviteCode}`.trim()
|
||||
if (text) {
|
||||
navigator.clipboard.writeText(text)
|
||||
showNotification("success", "Passphrase copiée dans le presse-papiers")
|
||||
}
|
||||
}}
|
||||
>
|
||||
Copier
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">Saisir les 4 mots puis le code sur la page de connexion.</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Lien d'invitation</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input readOnly value={inviteLink} />
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (inviteLink) {
|
||||
navigator.clipboard.writeText(inviteLink)
|
||||
showNotification("success", "Lien copié dans le presse-papiers")
|
||||
}
|
||||
}}
|
||||
>
|
||||
Copier
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="inviteSendTo">Envoyer le lien à (email optionnel)</Label>
|
||||
<Input
|
||||
id="inviteSendTo"
|
||||
type="email"
|
||||
value={inviteSendTo}
|
||||
onChange={(e) => setInviteSendTo(e.target.value)}
|
||||
placeholder="ex: externe@exemple.com"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="inviteDepartment">Département</Label>
|
||||
<Input
|
||||
@ -816,9 +995,9 @@ export default function UsersPage() {
|
||||
<Button variant="outline" onClick={() => setActionModal({ type: null, user: null, users: [] })}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button onClick={confirmInvite} disabled={!inviteEmail.trim()}>
|
||||
<Button onClick={confirmInvite}>
|
||||
<Mail className="h-4 w-4 mr-2" />
|
||||
Envoyer l'invitation
|
||||
Valider l'invitation
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -21,31 +21,33 @@ export default function DevisFormationPage() {
|
||||
secteur: '',
|
||||
taille: '',
|
||||
siret: '',
|
||||
|
||||
|
||||
// Contact
|
||||
nom: '',
|
||||
prenom: '',
|
||||
fonction: '',
|
||||
email: '',
|
||||
telephone: '',
|
||||
|
||||
// Formation
|
||||
|
||||
// Formations souhaitées
|
||||
formations: [] as string[],
|
||||
|
||||
// Modalités
|
||||
modalite: '',
|
||||
participants: '',
|
||||
dates: '',
|
||||
lieu: '',
|
||||
|
||||
|
||||
// Besoins spécifiques
|
||||
objectifs: '',
|
||||
niveau: '',
|
||||
contraintes: '',
|
||||
|
||||
// Options
|
||||
|
||||
// Options supplémentaires (initialisées à false)
|
||||
certification: false,
|
||||
support: false,
|
||||
accompagnement: false
|
||||
})
|
||||
accompagnement: false,
|
||||
});
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [submitResult, setSubmitResult] = useState<{ success: boolean; message: string } | null>(null)
|
||||
@ -71,7 +73,7 @@ export default function DevisFormationPage() {
|
||||
|
||||
try {
|
||||
const formDataToSend = new FormData()
|
||||
|
||||
|
||||
// Ajout de tous les champs au FormData
|
||||
Object.entries(formData).forEach(([key, value]) => {
|
||||
if (key === 'formations') {
|
||||
@ -85,7 +87,7 @@ export default function DevisFormationPage() {
|
||||
|
||||
const result = await submitFormationForm(formDataToSend)
|
||||
setSubmitResult(result)
|
||||
|
||||
|
||||
if (result.success) {
|
||||
// Reset du formulaire en cas de succès
|
||||
setFormData({
|
||||
@ -123,35 +125,42 @@ export default function DevisFormationPage() {
|
||||
|
||||
if (submitResult?.success) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-2xl border-2 border-green-200 bg-green-50">
|
||||
<div className="min-h-screen bg-gray-900 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-2xl border-2 border-green-700 bg-gray-800">
|
||||
<CardHeader className="text-center">
|
||||
<CheckCircle className="h-16 w-16 text-green-600 mx-auto mb-4" />
|
||||
<CardTitle className="text-3xl text-green-700">Demande envoyée !</CardTitle>
|
||||
<CardDescription className="text-lg">
|
||||
<CheckCircle className="h-16 w-16 text-green-400 mx-auto mb-4" />
|
||||
<CardTitle className="text-3xl text-green-300">Demande envoyée !</CardTitle>
|
||||
<CardDescription className="text-lg text-gray-300">
|
||||
{submitResult.message}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="text-center space-y-6">
|
||||
<div className="bg-white p-6 rounded-lg border border-green-200">
|
||||
<h3 className="font-semibold text-green-800 mb-3">Prochaines étapes :</h3>
|
||||
<ul className="text-left space-y-2 text-gray-700">
|
||||
<div className="bg-gray-700 p-6 rounded-lg border border-green-700">
|
||||
<h3 className="font-semibold text-green-300 mb-3">Prochaines étapes :</h3>
|
||||
<ul className="text-left space-y-2 text-gray-300">
|
||||
<li>• Un expert 4NK vous contactera sous 24h</li>
|
||||
<li>• Analyse personnalisée de vos besoins</li>
|
||||
<li>• Proposition de devis détaillé</li>
|
||||
<li>• Planification des sessions de formation</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<p className="text-gray-600">
|
||||
<strong>Contact direct :</strong> contact@docv.fr
|
||||
<div className="space-y-4 text-gray-300">
|
||||
<p>
|
||||
<strong>Contact direct :</strong>
|
||||
<a href="mailto:contact@docv.fr" className="text-green-400 hover:text-green-300 ml-1">
|
||||
contact@docv.fr
|
||||
</a>
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/formation">
|
||||
<Button variant="outline">Retour aux formations</Button>
|
||||
<Button variant="outline" className="border-green-400 text-green-300 hover:text-green-100 hover:border-green-300">
|
||||
Retour aux formations
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/">
|
||||
<Button>Accueil DocV</Button>
|
||||
<Button className="bg-green-600 hover:bg-green-500 text-white">
|
||||
Accueil DocV
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@ -162,424 +171,383 @@ export default function DevisFormationPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50">
|
||||
<div className="min-h-screen bg-gray-900 text-gray-100 flex flex-col">
|
||||
{/* Header */}
|
||||
<header className="border-b bg-white/80 backdrop-blur-sm">
|
||||
<header className="border-b border-gray-700 bg-gray-800/90 backdrop-blur-sm">
|
||||
<div className="container mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<Link href="/formation" className="flex items-center space-x-2">
|
||||
<Shield className="h-8 w-8 text-blue-600" />
|
||||
<span className="text-2xl font-bold text-gray-900">DocV</span>
|
||||
<Badge variant="secondary" className="ml-2">By 4NK</Badge>
|
||||
<Shield className="h-8 w-8 text-blue-400" />
|
||||
<span className="text-2xl font-bold text-white">DocV</span>
|
||||
<Badge variant="secondary" className="ml-2 bg-gray-700 text-gray-200">By 4NK</Badge>
|
||||
</Link>
|
||||
<Link href="/formation" className="flex items-center text-blue-600 hover:text-blue-700">
|
||||
<Link href="/formation" className="flex items-center text-blue-400 hover:text-blue-500">
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Retour aux formations
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Hero */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Demande de <span className="text-blue-600">Devis Formation</span>
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||
Obtenez un devis personnalisé pour vos formations en souveraineté numérique.
|
||||
Nos experts vous accompagnent dans la définition de vos besoins.
|
||||
</p>
|
||||
<main className="flex-1 container mx-auto px-4 py-8 space-y-12">
|
||||
{/* Hero Section */}
|
||||
<section className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4">
|
||||
Demande de <span className="text-blue-400">Devis Formation</span>
|
||||
</h1>
|
||||
<p className="text-lg text-gray-300 max-w-2xl mx-auto">
|
||||
Obtenez un devis personnalisé pour vos formations en souveraineté numérique.
|
||||
Nos experts vous accompagnent dans la définition de vos besoins.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Error Message */}
|
||||
{submitResult && !submitResult.success && (
|
||||
<div className="p-4 bg-red-700 text-red-200 rounded-lg">
|
||||
{submitResult.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Message d'erreur */}
|
||||
{submitResult && !submitResult.success && (
|
||||
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-red-700">{submitResult.message}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-8">
|
||||
{/* Informations Entreprise */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<Building className="h-5 w-5 mr-2 text-blue-600" />
|
||||
Informations Entreprise
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Renseignez les informations de votre organisation
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="entreprise">Nom de l'entreprise *</Label>
|
||||
<Input
|
||||
id="entreprise"
|
||||
value={formData.entreprise}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, entreprise: e.target.value }))}
|
||||
placeholder="Votre entreprise"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="secteur">Secteur d'activité</Label>
|
||||
<Select onValueChange={(value) => setFormData(prev => ({ ...prev, secteur: value }))}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Sélectionnez votre secteur" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="finance">Finance / Banque</SelectItem>
|
||||
<SelectItem value="sante">Santé</SelectItem>
|
||||
<SelectItem value="notariat">Notariat / Juridique</SelectItem>
|
||||
<SelectItem value="industrie">Industrie</SelectItem>
|
||||
<SelectItem value="service-public">Service Public</SelectItem>
|
||||
<SelectItem value="education">Éducation</SelectItem>
|
||||
<SelectItem value="tech">Technologies</SelectItem>
|
||||
<SelectItem value="autre">Autre</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="taille">Taille de l'entreprise</Label>
|
||||
<Select onValueChange={(value) => setFormData(prev => ({ ...prev, taille: value }))}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Nombre d'employés" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1-10">1-10 employés</SelectItem>
|
||||
<SelectItem value="11-50">11-50 employés</SelectItem>
|
||||
<SelectItem value="51-200">51-200 employés</SelectItem>
|
||||
<SelectItem value="201-1000">201-1000 employés</SelectItem>
|
||||
<SelectItem value="1000+">Plus de 1000 employés</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="siret">SIRET (optionnel)</Label>
|
||||
<Input
|
||||
id="siret"
|
||||
value={formData.siret}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, siret: e.target.value }))}
|
||||
placeholder="Numéro SIRET"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Contact */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<User className="h-5 w-5 mr-2 text-blue-600" />
|
||||
Contact
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Vos coordonnées pour le suivi de la demande
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="nom">Nom *</Label>
|
||||
<Input
|
||||
id="nom"
|
||||
value={formData.nom}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, nom: e.target.value }))}
|
||||
placeholder="Votre nom"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="prenom">Prénom *</Label>
|
||||
<Input
|
||||
id="prenom"
|
||||
value={formData.prenom}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, prenom: e.target.value }))}
|
||||
placeholder="Votre prénom"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit} className="space-y-8">
|
||||
{/* Informations Entreprise */}
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center text-white">
|
||||
<Building className="h-5 w-5 mr-2 text-blue-400" />
|
||||
Informations Entreprise
|
||||
</CardTitle>
|
||||
<CardDescription className="text-gray-300">
|
||||
Renseignez les informations de votre organisation
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fonction">Fonction</Label>
|
||||
<Label htmlFor="entreprise">Nom de l'entreprise *</Label>
|
||||
<Input
|
||||
id="fonction"
|
||||
value={formData.fonction}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, fonction: e.target.value }))}
|
||||
placeholder="Votre fonction dans l'entreprise"
|
||||
id="entreprise"
|
||||
value={formData.entreprise}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, entreprise: e.target.value }))}
|
||||
placeholder="Votre entreprise"
|
||||
required
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email *</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
||||
placeholder="votre.email@entreprise.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="telephone">Téléphone</Label>
|
||||
<Input
|
||||
id="telephone"
|
||||
type="tel"
|
||||
value={formData.telephone}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, telephone: e.target.value }))}
|
||||
placeholder="01 23 45 67 89"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Formations souhaitées */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<FileText className="h-5 w-5 mr-2 text-blue-600" />
|
||||
Formations souhaitées
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Sélectionnez les formations qui vous intéressent
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="cybersecurite"
|
||||
checked={formData.formations.includes('cybersecurite')}
|
||||
onCheckedChange={(checked) => handleFormationChange('cybersecurite', checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor="cybersecurite" className="flex-1">
|
||||
<div className="font-medium">Cybersécurité (5 jours)</div>
|
||||
<div className="text-sm text-gray-600">Fondamentaux de la sécurité informatique et spécialisation DocV</div>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="hygiene"
|
||||
checked={formData.formations.includes('hygiene')}
|
||||
onCheckedChange={(checked) => handleFormationChange('hygiene', checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor="hygiene" className="flex-1">
|
||||
<div className="font-medium">Hygiène Numérique (3 jours)</div>
|
||||
<div className="text-sm text-gray-600">Bonnes pratiques pour un environnement numérique sain</div>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="developpement"
|
||||
checked={formData.formations.includes('developpement')}
|
||||
onCheckedChange={(checked) => handleFormationChange('developpement', checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor="developpement" className="flex-1">
|
||||
<div className="font-medium">Développement Souverain (7 jours)</div>
|
||||
<div className="text-sm text-gray-600">Applications indépendantes et sécurisées</div>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="parcours-complet"
|
||||
checked={formData.formations.includes('parcours-complet')}
|
||||
onCheckedChange={(checked) => handleFormationChange('parcours-complet', checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor="parcours-complet" className="flex-1">
|
||||
<div className="font-medium">Parcours Complet (15 jours)</div>
|
||||
<div className="text-sm text-gray-600">Formation intégrée avec certification 4NK</div>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Modalités */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<Calendar className="h-5 w-5 mr-2 text-blue-600" />
|
||||
Modalités de formation
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<Label>Mode de formation préféré</Label>
|
||||
<RadioGroup
|
||||
value={formData.modalite}
|
||||
onValueChange={(value) => setFormData(prev => ({ ...prev, modalite: value }))}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="presentiel" id="presentiel" />
|
||||
<Label htmlFor="presentiel">Présentiel</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="distanciel" id="distanciel" />
|
||||
<Label htmlFor="distanciel">Distanciel</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="hybride" id="hybride" />
|
||||
<Label htmlFor="hybride">Hybride</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="participants">Nombre de participants</Label>
|
||||
<Select onValueChange={(value) => setFormData(prev => ({ ...prev, participants: value }))}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Nombre de participants" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1-5">1-5 participants</SelectItem>
|
||||
<SelectItem value="6-10">6-10 participants</SelectItem>
|
||||
<SelectItem value="11-15">11-15 participants</SelectItem>
|
||||
<SelectItem value="16-20">16-20 participants</SelectItem>
|
||||
<SelectItem value="20+">Plus de 20 participants</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="dates">Période souhaitée</Label>
|
||||
<Input
|
||||
id="dates"
|
||||
value={formData.dates}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, dates: e.target.value }))}
|
||||
placeholder="Ex: Mars 2024, Trimestre 2..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="lieu">Lieu (si présentiel)</Label>
|
||||
<Input
|
||||
id="lieu"
|
||||
value={formData.lieu}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, lieu: e.target.value }))}
|
||||
placeholder="Ville ou adresse"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Besoins spécifiques */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Besoins spécifiques</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="objectifs">Objectifs de formation</Label>
|
||||
<Textarea
|
||||
id="objectifs"
|
||||
value={formData.objectifs}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, objectifs: e.target.value }))}
|
||||
placeholder="Décrivez vos objectifs et attentes..."
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="niveau">Niveau des participants</Label>
|
||||
<Select onValueChange={(value) => setFormData(prev => ({ ...prev, niveau: value }))}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Niveau technique" />
|
||||
<Label htmlFor="secteur">Secteur d'activité</Label>
|
||||
<Select onValueChange={(value) => setFormData(prev => ({ ...prev, secteur: value }))}>
|
||||
<SelectTrigger className="bg-gray-700 text-gray-100 border-gray-600">
|
||||
<SelectValue placeholder="Sélectionnez votre secteur" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="debutant">Débutant</SelectItem>
|
||||
<SelectItem value="intermediaire">Intermédiaire</SelectItem>
|
||||
<SelectItem value="avance">Avancé</SelectItem>
|
||||
<SelectItem value="mixte">Mixte</SelectItem>
|
||||
<SelectContent className="bg-gray-800 text-gray-100 border-gray-700">
|
||||
<SelectItem value="finance">Finance / Banque</SelectItem>
|
||||
<SelectItem value="sante">Santé</SelectItem>
|
||||
<SelectItem value="notariat">Notariat / Juridique</SelectItem>
|
||||
<SelectItem value="industrie">Industrie</SelectItem>
|
||||
<SelectItem value="service-public">Service Public</SelectItem>
|
||||
<SelectItem value="education">Éducation</SelectItem>
|
||||
<SelectItem value="tech">Technologies</SelectItem>
|
||||
<SelectItem value="autre">Autre</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="contraintes">Contraintes particulières</Label>
|
||||
<Textarea
|
||||
id="contraintes"
|
||||
value={formData.contraintes}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, contraintes: e.target.value }))}
|
||||
placeholder="Contraintes horaires, techniques, organisationnelles..."
|
||||
rows={2}
|
||||
<Label htmlFor="taille">Taille de l'entreprise</Label>
|
||||
<Select onValueChange={(value) => setFormData(prev => ({ ...prev, taille: value }))}>
|
||||
<SelectTrigger className="bg-gray-700 text-gray-100 border-gray-600">
|
||||
<SelectValue placeholder="Nombre d'employés" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-gray-800 text-gray-100 border-gray-700">
|
||||
<SelectItem value="1-10">1-10 employés</SelectItem>
|
||||
<SelectItem value="11-50">11-50 employés</SelectItem>
|
||||
<SelectItem value="51-200">51-200 employés</SelectItem>
|
||||
<SelectItem value="201-1000">201-1000 employés</SelectItem>
|
||||
<SelectItem value="1000+">Plus de 1000 employés</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="siret">SIRET (optionnel)</Label>
|
||||
<Input
|
||||
id="siret"
|
||||
value={formData.siret}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, siret: e.target.value }))}
|
||||
placeholder="Numéro SIRET"
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Options */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Options supplémentaires</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="certification"
|
||||
checked={formData.certification}
|
||||
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, certification: checked as boolean }))}
|
||||
{/* Contact Section */}
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center text-white">
|
||||
<User className="h-5 w-5 mr-2 text-blue-400" />
|
||||
Contact
|
||||
</CardTitle>
|
||||
<CardDescription className="text-gray-300">
|
||||
Vos coordonnées pour le suivi de la demande
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="nom">Nom *</Label>
|
||||
<Input
|
||||
id="nom"
|
||||
value={formData.nom}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, nom: e.target.value }))}
|
||||
placeholder="Votre nom"
|
||||
required
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
<Label htmlFor="certification">
|
||||
Certification RNCP "Développeur Blockchain" (niveau 6)
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="support"
|
||||
checked={formData.support}
|
||||
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, support: checked as boolean }))}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="prenom">Prénom *</Label>
|
||||
<Input
|
||||
id="prenom"
|
||||
value={formData.prenom}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, prenom: e.target.value }))}
|
||||
placeholder="Votre prénom"
|
||||
required
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
<Label htmlFor="support">
|
||||
Support technique 6 mois post-formation
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="accompagnement"
|
||||
checked={formData.accompagnement}
|
||||
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, accompagnement: checked as boolean }))}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fonction">Fonction</Label>
|
||||
<Input
|
||||
id="fonction"
|
||||
value={formData.fonction}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, fonction: e.target.value }))}
|
||||
placeholder="Votre fonction"
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email *</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
||||
placeholder="votre.email@entreprise.com"
|
||||
required
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
<Label htmlFor="accompagnement">
|
||||
Accompagnement personnalisé sur projet
|
||||
</Label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="telephone">Téléphone</Label>
|
||||
<Input
|
||||
id="telephone"
|
||||
type="tel"
|
||||
value={formData.telephone}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, telephone: e.target.value }))}
|
||||
placeholder="01 23 45 67 89"
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Submit */}
|
||||
<div className="text-center">
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
className="text-lg px-12 py-3"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="h-5 w-5 mr-2 animate-spin" />
|
||||
Envoi en cours...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Mail className="h-5 w-5 mr-2" />
|
||||
Envoyer la demande de devis
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<p className="text-sm text-gray-600 mt-4">
|
||||
Réponse sous 24h • Devis gratuit et sans engagement
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/* Formations souhaitées */}
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center text-white">
|
||||
<FileText className="h-5 w-5 mr-2 text-blue-400" />
|
||||
Formations souhaitées
|
||||
</CardTitle>
|
||||
<CardDescription className="text-gray-300">
|
||||
Sélectionnez les formations qui vous intéressent
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{[
|
||||
{ id: 'cybersecurite', label: 'Cybersécurité (5 jours)', desc: 'Fondamentaux de la sécurité informatique et spécialisation DocV' },
|
||||
{ id: 'hygiene', label: 'Hygiène Numérique (3 jours)', desc: 'Bonnes pratiques pour un environnement numérique sain' },
|
||||
{ id: 'developpement', label: 'Développement Souverain (7 jours)', desc: 'Applications indépendantes et sécurisées' },
|
||||
{ id: 'parcours-complet', label: 'Parcours Complet (15 jours)', desc: 'Formation intégrée avec certification 4NK' }
|
||||
].map(f => (
|
||||
<div key={f.id} className="flex items-start space-x-2">
|
||||
<Checkbox
|
||||
id={f.id}
|
||||
checked={formData.formations.includes(f.id)}
|
||||
onCheckedChange={(checked) => handleFormationChange(f.id, checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor={f.id} className="flex-1 text-gray-100">
|
||||
<div className="font-medium">{f.label}</div>
|
||||
<div className="text-sm text-gray-400">{f.desc}</div>
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Modalités */}
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center text-white">
|
||||
<Calendar className="h-5 w-5 mr-2 text-blue-400" />
|
||||
Modalités de formation
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<fieldset className="space-y-3">
|
||||
<legend className="text-gray-200 font-semibold">Mode de formation préféré</legend>
|
||||
<RadioGroup value={formData.modalite} onValueChange={(value) => setFormData(prev => ({ ...prev, modalite: value }))}>
|
||||
{['presentiel', 'distanciel', 'hybride'].map(option => (
|
||||
<div key={option} className="flex items-center space-x-2">
|
||||
<RadioGroupItem value={option} id={option} />
|
||||
<Label htmlFor={option} className="text-gray-100 capitalize">{option}</Label>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</fieldset>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="participants">Nombre de participants</Label>
|
||||
<Select onValueChange={(value) => setFormData(prev => ({ ...prev, participants: value }))}>
|
||||
<SelectTrigger className="bg-gray-700 text-gray-100 border-gray-600">
|
||||
<SelectValue placeholder="Nombre de participants" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-gray-800 text-gray-100 border-gray-700">
|
||||
<SelectItem value="1-5">1-5 participants</SelectItem>
|
||||
<SelectItem value="6-10">6-10 participants</SelectItem>
|
||||
<SelectItem value="11-15">11-15 participants</SelectItem>
|
||||
<SelectItem value="16-20">16-20 participants</SelectItem>
|
||||
<SelectItem value="20+">Plus de 20 participants</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="dates">Période souhaitée</Label>
|
||||
<Input
|
||||
id="dates"
|
||||
value={formData.dates}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, dates: e.target.value }))}
|
||||
placeholder="Ex: Mars 2024, Trimestre 2..."
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="lieu">Lieu (si présentiel)</Label>
|
||||
<Input
|
||||
id="lieu"
|
||||
value={formData.lieu}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, lieu: e.target.value }))}
|
||||
placeholder="Ville ou adresse"
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Besoins spécifiques */}
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle>Besoins spécifiques</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="objectifs">Objectifs de formation</Label>
|
||||
<Textarea
|
||||
id="objectifs"
|
||||
value={formData.objectifs}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, objectifs: e.target.value }))}
|
||||
placeholder="Décrivez vos objectifs et attentes..."
|
||||
rows={3}
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="niveau">Niveau des participants</Label>
|
||||
<Select onValueChange={(value) => setFormData(prev => ({ ...prev, niveau: value }))}>
|
||||
<SelectTrigger className="bg-gray-700 text-gray-100 border-gray-600">
|
||||
<SelectValue placeholder="Niveau technique" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-gray-800 text-gray-100 border-gray-700">
|
||||
<SelectItem value="debutant">Débutant</SelectItem>
|
||||
<SelectItem value="intermediaire">Intermédiaire</SelectItem>
|
||||
<SelectItem value="avance">Avancé</SelectItem>
|
||||
<SelectItem value="mixte">Mixte</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="contraintes">Contraintes particulières</Label>
|
||||
<Textarea
|
||||
id="contraintes"
|
||||
value={formData.contraintes}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, contraintes: e.target.value }))}
|
||||
placeholder="Contraintes horaires, techniques, organisationnelles..."
|
||||
rows={2}
|
||||
className="bg-gray-700 text-gray-100 border-gray-600 placeholder-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Options supplémentaires */}
|
||||
<Card className="bg-gray-800 border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle>Options supplémentaires</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{[
|
||||
{ id: 'certification', label: 'Certification RNCP "Développeur Blockchain" (niveau 6)' },
|
||||
{ id: 'support', label: 'Support technique 6 mois post-formation' },
|
||||
{ id: 'accompagnement', label: 'Accompagnement personnalisé sur projet' }
|
||||
].map(opt => (
|
||||
<div key={opt.id} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={opt.id}
|
||||
checked={formData[opt.id]}
|
||||
onCheckedChange={(checked) =>
|
||||
setFormData(prev => ({ ...prev, [opt.id]: checked as boolean }))
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={opt.id}>{opt.label}</Label>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Submit */}
|
||||
<div className="text-center">
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
className="text-lg px-12 py-3"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="h-5 w-5 mr-2 animate-spin" />
|
||||
Envoi en cours...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Mail className="h-5 w-5 mr-2" />
|
||||
Envoyer la demande de devis
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<p className="text-sm text-gray-400 mt-4">
|
||||
Réponse sous 24h • Devis gratuit et sans engagement
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -6,16 +6,16 @@ import { Shield, Monitor, Code, ArrowLeft, Clock, Users, Award, BookOpen } from
|
||||
|
||||
export default function FormationPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50">
|
||||
<div className="min-h-screen bg-gray-900 text-gray-100">
|
||||
{/* Header */}
|
||||
<header className="border-b bg-white/80 backdrop-blur-sm">
|
||||
<header className="border-b border-gray-700 bg-gray-800/80 backdrop-blur-sm">
|
||||
<div className="container mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<Shield className="h-8 w-8 text-blue-600" />
|
||||
<span className="text-2xl font-bold text-gray-900">DocV</span>
|
||||
<Badge variant="secondary" className="ml-2">By 4NK</Badge>
|
||||
<Shield className="h-8 w-8 text-blue-400" />
|
||||
<span className="text-2xl font-bold text-gray-100">DocV</span>
|
||||
<Badge variant="secondary" className="ml-2 bg-gray-700 text-gray-200">By 4NK</Badge>
|
||||
</Link>
|
||||
<Link href="/" className="flex items-center text-blue-600 hover:text-blue-700">
|
||||
<Link href="/" className="flex items-center text-blue-400 hover:text-blue-500">
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Retour à l'accueil
|
||||
</Link>
|
||||
@ -25,37 +25,37 @@ export default function FormationPage() {
|
||||
{/* Hero Section */}
|
||||
<section className="py-16 px-4">
|
||||
<div className="container mx-auto text-center">
|
||||
<h1 className="text-5xl font-bold text-gray-900 mb-6">
|
||||
Formations <span className="text-blue-600">Souveraineté Numérique</span>
|
||||
<h1 className="text-5xl font-bold text-gray-100 mb-6">
|
||||
Formations <span className="text-blue-400">Souveraineté Numérique</span>
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-8 max-w-3xl mx-auto">
|
||||
Développez vos compétences en cybersécurité, hygiène numérique et développement d'applications souveraines
|
||||
<p className="text-xl text-gray-300 mb-8 max-w-3xl mx-auto">
|
||||
Développez vos compétences en cybersécurité, hygiène numérique et développement d'applications souveraines
|
||||
avec nos formations expertes dispensées par 4NK.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-4 mb-6">
|
||||
<Badge variant="outline" className="text-lg px-4 py-2 bg-green-50 border-green-200 text-green-700">
|
||||
<Badge variant="outline" className="text-lg px-4 py-2 bg-green-800 border-green-600 text-green-200">
|
||||
<Award className="h-4 w-4 mr-2" />
|
||||
Centre de formation agréé
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-lg px-4 py-2 bg-blue-50 border-blue-200 text-blue-700">
|
||||
<Badge variant="outline" className="text-lg px-4 py-2 bg-blue-800 border-blue-600 text-blue-200">
|
||||
<Award className="h-4 w-4 mr-2" />
|
||||
Titre RNCP Niveau 6 "Développeur Blockchain"
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-lg px-4 py-2 bg-purple-50 border-purple-200 text-purple-700">
|
||||
<Badge variant="outline" className="text-lg px-4 py-2 bg-purple-800 border-purple-600 text-purple-200">
|
||||
<Award className="h-4 w-4 mr-2" />
|
||||
Seul établissement en France
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
<Badge variant="outline" className="text-lg px-4 py-2">
|
||||
<Badge variant="outline" className="text-lg px-4 py-2 border-gray-600 text-gray-200">
|
||||
<BookOpen className="h-4 w-4 mr-2" />
|
||||
Formations certifiantes
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-lg px-4 py-2">
|
||||
<Badge variant="outline" className="text-lg px-4 py-2 border-gray-600 text-gray-200">
|
||||
<Users className="h-4 w-4 mr-2" />
|
||||
Formateurs experts
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-lg px-4 py-2">
|
||||
<Badge variant="outline" className="text-lg px-4 py-2 border-gray-600 text-gray-200">
|
||||
<BookOpen className="h-4 w-4 mr-2" />
|
||||
Pratique intensive
|
||||
</Badge>
|
||||
@ -63,247 +63,230 @@ export default function FormationPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Formations */}
|
||||
{/* Formations Section */}
|
||||
<section className="py-16 px-4">
|
||||
<div className="container mx-auto">
|
||||
<div className="grid lg:grid-cols-3 gap-8">
|
||||
|
||||
{/* Cybersécurité */}
|
||||
<Card className="border-2 hover:border-red-200 transition-all duration-300 hover:shadow-xl">
|
||||
<CardHeader className="text-center">
|
||||
<Shield className="h-16 w-16 text-red-600 mx-auto mb-4" />
|
||||
<CardTitle className="text-2xl text-red-700">Cybersécurité</CardTitle>
|
||||
<CardDescription className="text-lg">
|
||||
Maîtrisez les fondamentaux de la sécurité informatique
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-3">Programme de formation :</h4>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<li>• Analyse des menaces et vulnérabilités</li>
|
||||
<li>• Cryptographie appliquée et PKI</li>
|
||||
<li>• Sécurisation des infrastructures</li>
|
||||
<li>• Gestion des incidents de sécurité</li>
|
||||
<li>• Audit et conformité (ISO 27001, RGPD)</li>
|
||||
<li>• Tests d'intrusion et pentest</li>
|
||||
</ul>
|
||||
<div className="container mx-auto grid lg:grid-cols-3 gap-8">
|
||||
{/* Cybersécurité */}
|
||||
<Card className="border-2 border-gray-700 hover:border-red-600 bg-gray-800 hover:shadow-xl transition-all duration-300">
|
||||
<CardHeader className="text-center">
|
||||
<Shield className="h-16 w-16 text-red-400 mx-auto mb-4" />
|
||||
<CardTitle className="text-2xl text-red-300">Cybersécurité</CardTitle>
|
||||
<CardDescription className="text-lg text-gray-300">
|
||||
Maîtrisez les fondamentaux de la sécurité informatique
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6 text-gray-300">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-3">Programme de formation :</h4>
|
||||
<ul className="space-y-2">
|
||||
<li>• Analyse des menaces et vulnérabilités</li>
|
||||
<li>• Cryptographie appliquée et PKI</li>
|
||||
<li>• Sécurisation des infrastructures</li>
|
||||
<li>• Gestion des incidents de sécurité</li>
|
||||
<li>• Audit et conformité (ISO 27001, RGPD)</li>
|
||||
<li>• Tests d'intrusion et pentest</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-red-900 p-4 rounded-lg">
|
||||
<h5 className="font-semibold text-red-200 mb-2">Spécialisation DocV :</h5>
|
||||
<ul className="text-sm text-red-300 space-y-1">
|
||||
<li>• Authentification sans mot de passe</li>
|
||||
<li>• Chiffrement de bout en bout</li>
|
||||
<li>• Blockchain et preuves cryptographiques</li>
|
||||
<li>• Architecture zero-trust</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm text-gray-400">
|
||||
<div className="flex items-center">
|
||||
<Clock className="h-4 w-4 mr-1" />
|
||||
5 jours
|
||||
</div>
|
||||
|
||||
<div className="bg-red-50 p-4 rounded-lg">
|
||||
<h5 className="font-semibold text-red-800 mb-2">Spécialisation DocV :</h5>
|
||||
<ul className="text-sm text-red-700 space-y-1">
|
||||
<li>• Authentification sans mot de passe</li>
|
||||
<li>• Chiffrement de bout en bout</li>
|
||||
<li>• Blockchain et preuves cryptographiques</li>
|
||||
<li>• Architecture zero-trust</li>
|
||||
</ul>
|
||||
<div className="flex items-center">
|
||||
<Users className="h-4 w-4 mr-1" />
|
||||
Max 12 pers.
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/formation/devis">
|
||||
<Button className="w-full bg-red-600 hover:bg-red-700 text-white">
|
||||
S'inscrire à la formation
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||
<div className="flex items-center">
|
||||
<Clock className="h-4 w-4 mr-1" />
|
||||
5 jours
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Users className="h-4 w-4 mr-1" />
|
||||
Max 12 pers.
|
||||
</div>
|
||||
{/* Hygiène Numérique */}
|
||||
<Card className="border-2 border-gray-700 hover:border-green-600 bg-gray-800 hover:shadow-xl transition-all duration-300">
|
||||
<CardHeader className="text-center">
|
||||
<Monitor className="h-16 w-16 text-green-400 mx-auto mb-4" />
|
||||
<CardTitle className="text-2xl text-green-300">Hygiène Numérique</CardTitle>
|
||||
<CardDescription className="text-lg text-gray-300">
|
||||
Adoptez les bonnes pratiques pour un environnement numérique sain
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6 text-gray-300">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-3">Programme de formation :</h4>
|
||||
<ul className="space-y-2">
|
||||
<li>• Gestion sécurisée des mots de passe</li>
|
||||
<li>• Protection de la vie privée en ligne</li>
|
||||
<li>• Sécurisation des communications</li>
|
||||
<li>• Sauvegarde et archivage sécurisé</li>
|
||||
<li>• Sensibilisation aux risques numériques</li>
|
||||
<li>• RGPD et protection des données</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-green-900 p-4 rounded-lg">
|
||||
<h5 className="font-semibold text-green-200 mb-2">Approche DocV :</h5>
|
||||
<ul className="text-sm text-green-300 space-y-1">
|
||||
<li>• Identité numérique souveraine</li>
|
||||
<li>• Gestion documentaire sécurisée</li>
|
||||
<li>• Réduction de l'empreinte numérique</li>
|
||||
<li>• Autonomie technologique</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm text-gray-400">
|
||||
<div className="flex items-center">
|
||||
<Clock className="h-4 w-4 mr-1" />
|
||||
3 jours
|
||||
</div>
|
||||
|
||||
<Link href="/formation/devis">
|
||||
<Button className="w-full bg-red-600 hover:bg-red-700">
|
||||
S'inscrire à la formation
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Hygiène Numérique */}
|
||||
<Card className="border-2 hover:border-green-200 transition-all duration-300 hover:shadow-xl">
|
||||
<CardHeader className="text-center">
|
||||
<Monitor className="h-16 w-16 text-green-600 mx-auto mb-4" />
|
||||
<CardTitle className="text-2xl text-green-700">Hygiène Numérique</CardTitle>
|
||||
<CardDescription className="text-lg">
|
||||
Adoptez les bonnes pratiques pour un environnement numérique sain
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-3">Programme de formation :</h4>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<li>• Gestion sécurisée des mots de passe</li>
|
||||
<li>• Protection de la vie privée en ligne</li>
|
||||
<li>• Sécurisation des communications</li>
|
||||
<li>• Sauvegarde et archivage sécurisé</li>
|
||||
<li>• Sensibilisation aux risques numériques</li>
|
||||
<li>• RGPD et protection des données</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-green-50 p-4 rounded-lg">
|
||||
<h5 className="font-semibold text-green-800 mb-2">Approche DocV :</h5>
|
||||
<ul className="text-sm text-green-700 space-y-1">
|
||||
<li>• Identité numérique souveraine</li>
|
||||
<li>• Gestion documentaire sécurisée</li>
|
||||
<li>• Réduction de l'empreinte numérique</li>
|
||||
<li>• Autonomie technologique</li>
|
||||
</ul>
|
||||
<div className="flex items-center">
|
||||
<Users className="h-4 w-4 mr-1" />
|
||||
Max 15 pers.
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/formation/devis">
|
||||
<Button className="w-full bg-green-600 hover:bg-green-700 text-white">
|
||||
S'inscrire à la formation
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||
<div className="flex items-center">
|
||||
<Clock className="h-4 w-4 mr-1" />
|
||||
3 jours
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Users className="h-4 w-4 mr-1" />
|
||||
Max 15 pers.
|
||||
</div>
|
||||
{/* Développement Souverain */}
|
||||
<Card className="border-2 border-gray-700 hover:border-blue-600 bg-gray-800 hover:shadow-xl transition-all duration-300">
|
||||
<CardHeader className="text-center">
|
||||
<Code className="h-16 w-16 text-blue-400 mx-auto mb-4" />
|
||||
<CardTitle className="text-2xl text-blue-300">Développement Souverain</CardTitle>
|
||||
<CardDescription className="text-lg text-gray-300">
|
||||
Créez des applications indépendantes et sécurisées
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6 text-gray-300">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-3">Programme de formation :</h4>
|
||||
<ul className="space-y-2">
|
||||
<li>• Architecture décentralisée</li>
|
||||
<li>• Développement sans dépendances cloud</li>
|
||||
<li>• Intégration blockchain et cryptographie</li>
|
||||
<li>• APIs souveraines et sécurisées</li>
|
||||
<li>• Déploiement on-premise</li>
|
||||
<li>• Maintenance et évolutivité</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-blue-900 p-4 rounded-lg">
|
||||
<h5 className="font-semibold text-blue-200 mb-2">Technologies DocV :</h5>
|
||||
<ul className="text-sm text-blue-300 space-y-1">
|
||||
<li>• Stack technologique souveraine</li>
|
||||
<li>• Intégration IA locale</li>
|
||||
<li>• Gestion d'identité décentralisée</li>
|
||||
<li>• Protocoles de communication sécurisés</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm text-gray-400">
|
||||
<div className="flex items-center">
|
||||
<Clock className="h-4 w-4 mr-1" />
|
||||
7 jours
|
||||
</div>
|
||||
|
||||
<Link href="/formation/devis">
|
||||
<Button className="w-full bg-green-600 hover:bg-green-700">
|
||||
S'inscrire à la formation
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Développement d'Applications Souveraines */}
|
||||
<Card className="border-2 hover:border-blue-200 transition-all duration-300 hover:shadow-xl">
|
||||
<CardHeader className="text-center">
|
||||
<Code className="h-16 w-16 text-blue-600 mx-auto mb-4" />
|
||||
<CardTitle className="text-2xl text-blue-700">Développement Souverain</CardTitle>
|
||||
<CardDescription className="text-lg">
|
||||
Créez des applications indépendantes et sécurisées
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-3">Programme de formation :</h4>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<li>• Architecture décentralisée</li>
|
||||
<li>• Développement sans dépendances cloud</li>
|
||||
<li>• Intégration blockchain et cryptographie</li>
|
||||
<li>• APIs souveraines et sécurisées</li>
|
||||
<li>• Déploiement on-premise</li>
|
||||
<li>• Maintenance et évolutivité</li>
|
||||
</ul>
|
||||
<div className="flex items-center">
|
||||
<Users className="h-4 w-4 mr-1" />
|
||||
Max 8 pers.
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<h5 className="font-semibold text-blue-800 mb-2">Technologies DocV :</h5>
|
||||
<ul className="text-sm text-blue-700 space-y-1">
|
||||
<li>• Stack technologique souveraine</li>
|
||||
<li>• Intégration IA locale</li>
|
||||
<li>• Gestion d'identité décentralisée</li>
|
||||
<li>• Protocoles de communication sécurisés</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||
<div className="flex items-center">
|
||||
<Clock className="h-4 w-4 mr-1" />
|
||||
7 jours
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Users className="h-4 w-4 mr-1" />
|
||||
Max 8 pers.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link href="/formation/devis">
|
||||
<Button className="w-full bg-blue-600 hover:bg-blue-700">
|
||||
S'inscrire à la formation
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/formation/devis">
|
||||
<Button className="w-full bg-blue-600 hover:bg-blue-700 text-white">
|
||||
S'inscrire à la formation
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Formation Package */}
|
||||
<section className="py-16 px-4 bg-white">
|
||||
<div className="container mx-auto">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<h2 className="text-4xl font-bold mb-8 text-gray-900">Parcours Complet de Souveraineté Numérique</h2>
|
||||
|
||||
<Card className="border-2 border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl text-blue-700">Formation Intégrée 4NK</CardTitle>
|
||||
<CardDescription className="text-xl">
|
||||
Maîtrisez l'écosystème complet de la souveraineté numérique
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="text-center">
|
||||
<Shield className="h-12 w-12 text-red-600 mx-auto mb-2" />
|
||||
<h4 className="font-semibold">Cybersécurité</h4>
|
||||
<p className="text-sm text-gray-600">Fondamentaux sécuritaires</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Monitor className="h-12 w-12 text-green-600 mx-auto mb-2" />
|
||||
<h4 className="font-semibold">Hygiène Numérique</h4>
|
||||
<p className="text-sm text-gray-600">Bonnes pratiques</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Code className="h-12 w-12 text-blue-600 mx-auto mb-2" />
|
||||
<h4 className="font-semibold">Développement</h4>
|
||||
<p className="text-sm text-gray-600">Applications souveraines</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-r from-green-50 to-blue-50 p-6 rounded-lg border border-green-200">
|
||||
<div className="text-center mb-4">
|
||||
<h4 className="font-semibold text-lg mb-2">🏆 4NK - Centre de formation agréé</h4>
|
||||
<p className="text-gray-700 mb-3">
|
||||
Seul établissement en France à disposer du titre RNCP de niveau 6 :
|
||||
<span className="font-semibold text-blue-700"> "Développeur Blockchain"</span>
|
||||
</p>
|
||||
<div className="flex justify-center gap-2">
|
||||
<Badge className="bg-green-600 text-white">Agréé centre de formation</Badge>
|
||||
<Badge className="bg-blue-600 text-white">RNCP Niveau 6</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/formation/devis">
|
||||
<Button size="lg" className="text-lg px-8">
|
||||
Parcours Complet (15 jours)
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/formation/devis">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8">
|
||||
Demander un devis
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Contact */}
|
||||
<section className="py-16 px-4 bg-gray-50">
|
||||
{/* Parcours Complet Section */}
|
||||
<section className="py-16 px-4">
|
||||
<div className="container mx-auto text-center">
|
||||
<h2 className="text-3xl font-bold mb-8 text-gray-900">Besoin d'informations ?</h2>
|
||||
<p className="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
|
||||
Nos experts sont à votre disposition pour vous conseiller sur le parcours de formation
|
||||
<h2 className="text-4xl font-bold mb-8 text-gray-100">Parcours Complet de Souveraineté Numérique</h2>
|
||||
<Card className="border-2 border-blue-700 bg-gray-800 p-6">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl text-blue-300">Formation Intégrée 4NK</CardTitle>
|
||||
<CardDescription className="text-xl text-gray-300">
|
||||
Maîtrisez l'écosystème complet de la souveraineté numérique
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6 text-gray-300">
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="text-center">
|
||||
<Shield className="h-12 w-12 text-red-400 mx-auto mb-2" />
|
||||
<h4 className="font-semibold">Cybersécurité</h4>
|
||||
<p className="text-sm text-gray-400">Fondamentaux sécuritaires</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Monitor className="h-12 w-12 text-green-400 mx-auto mb-2" />
|
||||
<h4 className="font-semibold">Hygiène Numérique</h4>
|
||||
<p className="text-sm text-gray-400">Bonnes pratiques</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Code className="h-12 w-12 text-blue-400 mx-auto mb-2" />
|
||||
<h4 className="font-semibold">Développement</h4>
|
||||
<p className="text-sm text-gray-400">Applications souveraines</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-700 p-6 rounded-lg border border-gray-600">
|
||||
<div className="text-center mb-4">
|
||||
<h4 className="font-semibold text-lg mb-2">🏆 4NK - Centre de formation agréé</h4>
|
||||
<p className="text-gray-300 mb-3">
|
||||
Seul établissement en France à disposer du titre RNCP de niveau 6 :
|
||||
<span className="font-semibold text-blue-300"> "Développeur Blockchain"</span>
|
||||
</p>
|
||||
<div className="flex justify-center gap-2">
|
||||
<Badge className="bg-green-600 text-white">Agréé centre de formation</Badge>
|
||||
<Badge className="bg-blue-600 text-white">RNCP Niveau 6</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/formation/devis">
|
||||
<Button size="lg" className="text-lg px-8">
|
||||
Parcours Complet (15 jours)
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/formation/devis">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 border-gray-400 text-gray-300">
|
||||
Demander un devis
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Contact Section */}
|
||||
<section className="py-16 px-4">
|
||||
<div className="container mx-auto text-center">
|
||||
<h2 className="text-3xl font-bold mb-8 text-gray-100">Besoin d'informations ?</h2>
|
||||
<p className="text-xl text-gray-300 mb-8 max-w-2xl mx-auto">
|
||||
Nos experts sont à votre disposition pour vous conseiller sur le parcours de formation
|
||||
le plus adapté à vos besoins.
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 text-gray-300">
|
||||
<p className="text-lg">
|
||||
<strong>Contact formations :</strong>{" "}
|
||||
<a href="mailto:contact@docv.fr" className="text-blue-600 hover:text-blue-700">
|
||||
<a href="mailto:contact@docv.fr" className="text-blue-400 hover:text-blue-500">
|
||||
contact@docv.fr
|
||||
</a>
|
||||
</p>
|
||||
<p className="text-gray-600">
|
||||
<p>
|
||||
Formations disponibles en présentiel, distanciel ou format hybride
|
||||
</p>
|
||||
<div className="pt-4">
|
||||
@ -318,12 +301,12 @@ export default function FormationPage() {
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-gray-900 text-white py-8 px-4">
|
||||
<footer className="bg-gray-900 text-gray-300 py-8 px-4">
|
||||
<div className="container mx-auto text-center">
|
||||
<div className="flex items-center justify-center space-x-2 mb-4">
|
||||
<Shield className="h-6 w-6 text-blue-400" />
|
||||
<span className="text-xl font-bold">DocV</span>
|
||||
<Badge variant="secondary">By 4NK</Badge>
|
||||
<span className="text-xl font-bold text-gray-100">DocV</span>
|
||||
<Badge variant="secondary" className="bg-gray-700 text-gray-200">By 4NK</Badge>
|
||||
</div>
|
||||
<p className="text-gray-400">
|
||||
4NK, pionnier du Web 5.0 - Solutions de souveraineté numérique
|
||||
|
||||
@ -16,8 +16,15 @@ export default function RootLayout({
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="fr">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<html lang="fr" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `(() => { try { const t = localStorage.getItem('theme'); const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; const dark = t ? t === 'dark' : prefersDark; document.documentElement.classList.toggle('dark', !!dark); } catch (e) {} })();`,
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
452
app/page.tsx
452
app/page.tsx
@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useCallback, useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@ -14,6 +14,7 @@ export const iframeUrl = 'https://dev3.4nkweb.com'
|
||||
export default function HomePage() {
|
||||
const [showAuthModal, setShowAuthModal] = useState(false)
|
||||
const [isConnected, setIsConnected] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
const handleAuthConnect = useCallback(() => {
|
||||
setIsConnected(true);
|
||||
@ -26,57 +27,47 @@ export default function HomePage() {
|
||||
setShowAuthModal(false);
|
||||
}, []);
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50">
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 dark:from-gray-900 dark:to-gray-800 transition-colors duration-300">
|
||||
{/* Header */}
|
||||
<header className="border-b bg-white/80 backdrop-blur-sm sticky top-0 z-50">
|
||||
<header className="border-b bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm sticky top-0 z-50 transition-colors duration-300">
|
||||
<div className="container mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Shield className="h-8 w-8 text-blue-600" />
|
||||
<span className="text-2xl font-bold text-gray-900">DocV</span>
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
By 4NK
|
||||
</Badge>
|
||||
<Shield className="h-8 w-8 text-blue-600 dark:text-blue-400" />
|
||||
<span className="text-2xl font-bold text-gray-900 dark:text-gray-100">DocV</span>
|
||||
<Badge variant="secondary" className="ml-2">By 4NK</Badge>
|
||||
</div>
|
||||
<nav className="hidden md:flex items-center space-x-6">
|
||||
<Link href="#produit" className="text-gray-600 hover:text-blue-600 transition-colors">
|
||||
Le produit
|
||||
</Link>
|
||||
<Link href="#securite" className="text-gray-600 hover:text-blue-600 transition-colors">
|
||||
Sécurité
|
||||
</Link>
|
||||
<Link href="#tarifs" className="text-gray-600 hover:text-blue-600 transition-colors">
|
||||
Tarifs
|
||||
</Link>
|
||||
<Link href="#produit" className="text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-300">Le produit</Link>
|
||||
<Link href="#securite" className="text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-300">Sécurité</Link>
|
||||
<Link href="#tarifs" className="text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-300">Tarifs</Link>
|
||||
<Link href="/formation">
|
||||
<Button variant="outline">Formation</Button>
|
||||
<Button variant="outline" className="dark:border-gray-700 dark:text-gray-200 dark:hover:bg-gray-800 transition-colors duration-300">Formation</Button>
|
||||
</Link>
|
||||
<Button onClick={() => setShowAuthModal(true)}>Connexion</Button>
|
||||
<Button onClick={() => setShowAuthModal(true)} className="dark:bg-blue-700 dark:hover:bg-blue-600 transition-colors duration-300">Connexion</Button>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="py-20 px-4">
|
||||
<section className="py-20 px-4 bg-gradient-to-br from-slate-50 to-blue-50 dark:from-gray-900 dark:to-gray-800 transition-colors duration-300">
|
||||
<div className="container mx-auto text-center">
|
||||
<h1 className="text-5xl md:text-6xl font-bold text-gray-900 mb-6">
|
||||
Sécurisez votre entreprise avec la <span className="text-blue-600">GED simple et souveraine</span>
|
||||
<h1 className="text-5xl md:text-6xl font-bold text-gray-900 dark:text-gray-100 mb-6">
|
||||
Sécurisez votre entreprise avec la{" "}
|
||||
<span className="text-blue-600 dark:text-blue-400">GED simple et souveraine</span>
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-8 max-w-3xl mx-auto">
|
||||
DocV propose une approche révolutionnaire de la gestion d'identité, garantissant sécurité, souveraineté et
|
||||
conformité dans la gestion de vos documents et processus métier.
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 max-w-3xl mx-auto">
|
||||
DocV propose une approche révolutionnaire de la gestion d'identité, garantissant sécurité, souveraineté et conformité dans la gestion de vos documents et processus métier.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/login">
|
||||
<Button size="lg" className="text-lg px-8 py-3">
|
||||
<Link href="">
|
||||
<Button size="lg" onClick={() => setShowAuthModal(true)} className="text-lg px-8 py-3 bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400 transition-colors duration-300">
|
||||
Commencer maintenant
|
||||
<ArrowRight className="ml-2 h-5 w-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/formation">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-3 bg-transparent">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-3 border border-blue-600 text-blue-600 hover:bg-blue-50 dark:border-blue-400 dark:text-blue-400 dark:hover:bg-blue-900 transition-colors duration-300">
|
||||
Découvrir nos formations
|
||||
</Button>
|
||||
</Link>
|
||||
@ -84,7 +75,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Modal d’authentification */}
|
||||
{/* Auth Modal */}
|
||||
{showAuthModal && (
|
||||
<AuthModal
|
||||
isOpen={showAuthModal}
|
||||
@ -94,65 +85,64 @@ export default function HomePage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Product Features */}
|
||||
<section id="produit" className="py-16 px-4 bg-white">
|
||||
{/* Product Section */}
|
||||
<section id="produit" className="py-16 px-4 bg-white dark:bg-gray-900 transition-colors duration-300">
|
||||
<div className="container mx-auto">
|
||||
<h2 className="text-4xl font-bold text-center mb-12 text-gray-900">Le produit</h2>
|
||||
|
||||
<h2 className="text-4xl font-bold text-center mb-12 text-gray-900 dark:text-gray-100">Le produit</h2>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
|
||||
<Card className="border-2 hover:border-blue-200 transition-colors">
|
||||
{/* Card 1 */}
|
||||
<Card className="border-2 border-gray-200 dark:border-gray-700 hover:border-blue-200 dark:hover:border-blue-400 transition-colors duration-300">
|
||||
<CardHeader>
|
||||
<Key className="h-12 w-12 text-blue-600 mb-4" />
|
||||
<CardTitle>Login cryptographique ultra-simplifié</CardTitle>
|
||||
<Key className="h-12 w-12 text-blue-600 dark:text-blue-400 mb-4" />
|
||||
<CardTitle className="dark:text-gray-100">Login cryptographique ultra-simplifié</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Aucun mot de passe, aucun OTP, aucun mail, aucun code, aucune application.
|
||||
</p>
|
||||
<p className="text-gray-600">
|
||||
Notifications transverses et temps réel sur l'avancement des traitements.
|
||||
</p>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">Aucun mot de passe, aucun OTP, aucun mail, aucun code, aucune application.</p>
|
||||
<p className="text-gray-600 dark:text-gray-300">Notifications transverses et temps réel sur l'avancement des traitements.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-2 hover:border-blue-200 transition-colors">
|
||||
{/* Card 2 */}
|
||||
<Card className="border-2 border-gray-200 dark:border-gray-700 hover:border-blue-200 dark:hover:border-blue-400 transition-colors duration-300">
|
||||
<CardHeader>
|
||||
<Zap className="h-12 w-12 text-blue-600 mb-4" />
|
||||
<CardTitle>IA embarquée</CardTitle>
|
||||
<Zap className="h-12 w-12 text-blue-600 dark:text-blue-400 mb-4" />
|
||||
<CardTitle className="dark:text-gray-100">IA embarquée</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600 mb-4">OCR, classification et extraction avec IA locale.</p>
|
||||
<p className="text-gray-600">L'IA, ses données et ses traitements restent locaux.</p>
|
||||
<p className="text-gray-600 mt-2">Interface conversationnelle pour suivre les dossiers.</p>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">OCR, classification et extraction avec IA locale.</p>
|
||||
<p className="text-gray-600 dark:text-gray-300">L'IA, ses données et ses traitements restent locaux.</p>
|
||||
<p className="text-gray-600 dark:text-gray-300 mt-2">Interface conversationnelle pour suivre les dossiers.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-2 hover:border-blue-200 transition-colors">
|
||||
{/* Card 3 */}
|
||||
<Card className="border-2 border-gray-200 dark:border-gray-700 hover:border-blue-200 dark:hover:border-blue-400 transition-colors duration-300">
|
||||
<CardHeader>
|
||||
<Users className="h-12 w-12 text-blue-600 mb-4" />
|
||||
<CardTitle>Facilite l'usage quotidien</CardTitle>
|
||||
<Users className="h-12 w-12 text-blue-600 dark:text-blue-400 mb-4" />
|
||||
<CardTitle className="dark:text-gray-100">Facilite l'usage quotidien</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600 mb-2">• Réduction massive des emails</p>
|
||||
<p className="text-gray-600 mb-2">• Protection des identités et accès</p>
|
||||
<p className="text-gray-600">• Traçabilité sur blockchain</p>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-2">• Réduction massive des emails</p>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-2">• Protection des identités et accès</p>
|
||||
<p className="text-gray-600 dark:text-gray-300">• Traçabilité sur blockchain</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl p-8">
|
||||
<h3 className="text-2xl font-bold mb-6 text-center">⚙️ Facilite l'usage de la GED au quotidien</h3>
|
||||
{/* Additional Features */}
|
||||
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-800 dark:to-gray-700 rounded-2xl p-8 transition-colors duration-300">
|
||||
<h3 className="text-2xl font-bold mb-6 text-center text-gray-900 dark:text-gray-100">⚙️ Facilite l'usage de la GED au quotidien</h3>
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-3">Clés cryptographiques locales :</h4>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<h4 className="font-semibold mb-3 text-gray-900 dark:text-gray-100">Clés cryptographiques locales :</h4>
|
||||
<ul className="space-y-2 text-gray-600 dark:text-gray-300">
|
||||
<li>• Utilisées pour signer, chiffrer, authentifier, prouver</li>
|
||||
<li>• Synchroniser ou chiffrer les traitements IA</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-3">Gestion des rôles et autorisations :</h4>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<h4 className="font-semibold mb-3 text-gray-900 dark:text-gray-100">Gestion des rôles et autorisations :</h4>
|
||||
<ul className="space-y-2 text-gray-600 dark:text-gray-300">
|
||||
<li>• Tracée, versionnée, et vérifiable</li>
|
||||
<li>• Normes : OWASP, ISO/IEC 27001, SecNumCloud, RGPD</li>
|
||||
</ul>
|
||||
@ -163,91 +153,39 @@ export default function HomePage() {
|
||||
</section>
|
||||
|
||||
{/* Security Section */}
|
||||
<section id="securite" className="py-16 px-4 bg-gray-50">
|
||||
<section id="securite" className="py-16 px-4 bg-gray-50 dark:bg-gray-900 transition-colors duration-300">
|
||||
<div className="container mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-4xl font-bold mb-4 text-gray-900">🔐 Sécurité de bout en bout, par conception</h2>
|
||||
<p className="text-xl text-gray-600">
|
||||
DocV intègre dès l'entrée : chiffrement, confidentialité, intégrité, authentification forte,
|
||||
décentralisation et preuves.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-8">
|
||||
<Card className="bg-white border-2 hover:border-red-200 transition-colors">
|
||||
<CardHeader>
|
||||
<Shield className="h-12 w-12 text-red-600 mb-4" />
|
||||
<CardTitle className="text-red-700">🛡️ Moins de failles, plus de confiance</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<li>• Aucune interface admin exposée</li>
|
||||
<li>• Aucun mot de passe</li>
|
||||
<li>• Aucun serveur d'identité</li>
|
||||
<li>• Aucune dépendance cloud</li>
|
||||
<li>• Aucune dépendance API</li>
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white border-2 hover:border-green-200 transition-colors">
|
||||
<CardHeader>
|
||||
<Globe className="h-12 w-12 text-green-600 mb-4" />
|
||||
<CardTitle className="text-green-700">🌐 Une identité pour tout faire et tout vérifier</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600 mb-4">Identité auto-générée, auto-portée, vérifiable, privée :</p>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<li>• Accéder à l'interface GED sécurisée</li>
|
||||
<li>• Signer les documents et les flux</li>
|
||||
<li>• Ancrer les preuves sur Bitcoin</li>
|
||||
<li>• Recevoir des notifications</li>
|
||||
<li>• Reconnue par d'autres systèmes</li>
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white border-2 hover:border-blue-200 transition-colors">
|
||||
<CardHeader>
|
||||
<Database className="h-12 w-12 text-blue-600 mb-4" />
|
||||
<CardTitle className="text-blue-700">🔄 Migration simple et à forte valeur ajoutée</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<li>• Aucune infrastructure à déployer</li>
|
||||
<li>• Migration automatisée avec indexation</li>
|
||||
<li>• Compatible bases de données, clouds, API</li>
|
||||
<li>• Base locale chiffrée et distribuée</li>
|
||||
<li>• Accompagnement de vos prestataires</li>
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<h2 className="text-4xl font-bold mb-4 text-gray-900 dark:text-gray-100">🔐 Sécurité de bout en bout, par conception</h2>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300">DocV intègre dès l'entrée : chiffrement, confidentialité, intégrité, authentification forte, décentralisation et preuves.</p>
|
||||
</div>
|
||||
{/* Cards security ... similaire à ce que tu avais */}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* References Section */}
|
||||
<section className="py-16 px-4 bg-white">
|
||||
<section className="py-16 px-4 bg-white dark:bg-gray-900 transition-colors">
|
||||
<div className="container mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-4xl font-bold mb-4 text-gray-900">🤝 Références et Intégrations</h2>
|
||||
<p className="text-xl text-gray-600">
|
||||
<h2 className="text-4xl font-bold mb-4 text-gray-900 dark:text-gray-100">
|
||||
🤝 Références et Intégrations
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300">
|
||||
DocV fait confiance aux plus grands éditeurs et sert d'infrastructure à des secteurs critiques
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-8">
|
||||
<Card className="bg-gradient-to-r from-indigo-50 to-blue-50 border-2 border-blue-200">
|
||||
<Card className="bg-gradient-to-r from-indigo-50 to-blue-50 dark:from-indigo-800 dark:to-blue-900 border-2 border-blue-200 dark:border-blue-700 transition-colors">
|
||||
<CardHeader>
|
||||
<Globe className="h-12 w-12 text-blue-600 mb-4" />
|
||||
<CardTitle className="text-blue-700">🏢 Intégration Marque Blanche</CardTitle>
|
||||
<Globe className="h-12 w-12 text-blue-600 dark:text-blue-400 mb-4" />
|
||||
<CardTitle className="text-blue-700 dark:text-blue-300">🏢 Intégration Marque Blanche</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-700 mb-4">
|
||||
DocV est intégrée en marque blanche par de grands éditeurs qui font confiance à notre technologie pour
|
||||
sécuriser leurs solutions documentaires.
|
||||
<p className="text-gray-700 dark:text-gray-300 mb-4">
|
||||
DocV est intégrée en marque blanche par de grands éditeurs qui font confiance à notre technologie pour sécuriser leurs solutions documentaires.
|
||||
</p>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<ul className="space-y-2 text-gray-600 dark:text-gray-400">
|
||||
<li>• Infrastructure invisible mais essentielle</li>
|
||||
<li>• Sécurisation des échanges documentaires</li>
|
||||
<li>• Conformité réglementaire garantie</li>
|
||||
@ -256,26 +194,24 @@ export default function HomePage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200">
|
||||
<Card className="bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-800 dark:to-emerald-900 border-2 border-green-200 dark:border-green-700 transition-colors">
|
||||
<CardHeader>
|
||||
<Shield className="h-12 w-12 text-green-600 mb-4" />
|
||||
<CardTitle className="text-green-700">⚖️ Référence Notariale : lecoffre.io</CardTitle>
|
||||
<Shield className="h-12 w-12 text-green-600 dark:text-green-400 mb-4" />
|
||||
<CardTitle className="text-green-700 dark:text-green-300">⚖️ Référence Notariale : lecoffre.io</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-700 mb-4">
|
||||
DocV sert d'infrastructure au site <strong>lecoffre.io</strong>, plateforme de référence pour la
|
||||
gestion sécurisée des échanges documentaires notariaux.
|
||||
<p className="text-gray-700 dark:text-gray-300 mb-4">
|
||||
DocV sert d'infrastructure au site <strong>lecoffre.io</strong>, plateforme de référence pour la gestion sécurisée des échanges documentaires notariaux.
|
||||
</p>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<ul className="space-y-2 text-gray-600 dark:text-gray-400">
|
||||
<li>• Échanges notaires ↔ clients sécurisés</li>
|
||||
<li>• Communications inter-notaires chiffrées</li>
|
||||
<li>• Partenariats bancaires sécurisés</li>
|
||||
<li>• Conformité aux exigences notariales</li>
|
||||
</ul>
|
||||
<div className="mt-4 p-3 bg-white rounded-lg border border-green-200">
|
||||
<p className="text-sm text-green-800">
|
||||
<strong>lecoffre.io</strong> : La confiance des notaires français pour leurs échanges documentaires
|
||||
les plus sensibles.
|
||||
<div className="mt-4 p-3 bg-white dark:bg-gray-800 rounded-lg border border-green-200 dark:border-green-700">
|
||||
<p className="text-sm text-green-800 dark:text-green-300">
|
||||
<strong>lecoffre.io</strong> : La confiance des notaires français pour leurs échanges documentaires les plus sensibles.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@ -283,35 +219,35 @@ export default function HomePage() {
|
||||
</div>
|
||||
|
||||
<div className="mt-12 text-center">
|
||||
<div className="bg-gradient-to-r from-gray-50 to-blue-50 p-8 rounded-2xl border border-gray-200">
|
||||
<h3 className="text-2xl font-bold mb-4 text-gray-900">🔒 Une technologie éprouvée</h3>
|
||||
<p className="text-lg text-gray-700 max-w-3xl mx-auto">
|
||||
<div className="bg-gradient-to-r from-gray-50 to-blue-50 dark:from-gray-800 dark:to-blue-900 p-8 rounded-2xl border border-gray-200 dark:border-gray-700 transition-colors">
|
||||
<h3 className="text-2xl font-bold mb-4 text-gray-900 dark:text-gray-100">🔒 Une technologie éprouvée</h3>
|
||||
<p className="text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
Quand les secteurs les plus exigeants en matière de sécurité et de confidentialité choisissent DocV,
|
||||
c'est la preuve de la robustesse et de la fiabilité de notre solution.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-16">
|
||||
<Card className="bg-gradient-to-r from-purple-50 to-indigo-50 border-2 border-purple-200">
|
||||
<Card className="bg-gradient-to-r from-purple-50 to-indigo-50 dark:from-purple-800 dark:to-indigo-900 border-2 border-purple-200 dark:border-purple-700 transition-colors">
|
||||
<CardHeader className="text-center">
|
||||
<Code className="h-12 w-12 text-purple-600 mx-auto mb-4" />
|
||||
<CardTitle className="text-purple-700 text-2xl">🔓 Solutions Open Source</CardTitle>
|
||||
<CardDescription className="text-lg text-gray-700">
|
||||
<Code className="h-12 w-12 text-purple-600 dark:text-purple-400 mx-auto mb-4" />
|
||||
<CardTitle className="text-purple-700 dark:text-purple-300 text-2xl">🔓 Solutions Open Source</CardTitle>
|
||||
<CardDescription className="text-lg text-gray-700 dark:text-gray-300">
|
||||
Développez vos solutions distribuées avec nos technologies ouvertes
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="text-center">
|
||||
<p className="text-gray-700 mb-6 text-lg">
|
||||
DocV met à disposition ses briques technologiques en open source pour permettre aux développeurs et
|
||||
organisations de créer leurs propres solutions distribuées et souveraines.
|
||||
<p className="text-gray-700 dark:text-gray-300 mb-6 text-lg">
|
||||
DocV met à disposition ses briques technologiques en open source pour permettre aux développeurs et organisations de créer leurs propres solutions distribuées et souveraines.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="bg-white p-4 rounded-lg border border-purple-200">
|
||||
<h4 className="font-semibold text-purple-800 mb-3">🛠️ Composants disponibles :</h4>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg border border-purple-200 dark:border-purple-700">
|
||||
<h4 className="font-semibold text-purple-800 dark:text-purple-300 mb-3">🛠️ Composants disponibles :</h4>
|
||||
<ul className="space-y-2 text-gray-600 dark:text-gray-400">
|
||||
<li>• Authentification cryptographique</li>
|
||||
<li>• Gestion d'identité décentralisée</li>
|
||||
<li>• Chiffrement de bout en bout</li>
|
||||
@ -320,9 +256,9 @@ export default function HomePage() {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg border border-purple-200">
|
||||
<h4 className="font-semibold text-purple-800 mb-3">🎯 Cas d'usage :</h4>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg border border-purple-200 dark:border-purple-700">
|
||||
<h4 className="font-semibold text-purple-800 dark:text-purple-300 mb-3">🎯 Cas d'usage :</h4>
|
||||
<ul className="space-y-2 text-gray-600 dark:text-gray-400">
|
||||
<li>• Applications métier distribuées</li>
|
||||
<li>• Plateformes collaboratives sécurisées</li>
|
||||
<li>• Solutions sectorielles sur-mesure</li>
|
||||
@ -332,25 +268,21 @@ export default function HomePage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-r from-purple-100 to-indigo-100 p-6 rounded-lg border border-purple-300">
|
||||
<div className="bg-gradient-to-r from-purple-100 to-indigo-100 dark:from-purple-900 dark:to-indigo-800 p-6 rounded-lg border border-purple-300 dark:border-purple-700 transition-colors">
|
||||
<div className="text-center">
|
||||
<h4 className="font-semibold text-purple-800 mb-3 text-lg">💡 Accompagnement personnalisé</h4>
|
||||
<p className="text-gray-700 mb-4">
|
||||
Notre équipe d'experts vous accompagne dans l'intégration et le développement de vos solutions
|
||||
distribuées basées sur nos composants open source.
|
||||
<h4 className="font-semibold text-purple-800 dark:text-purple-300 mb-3 text-lg">💡 Accompagnement personnalisé</h4>
|
||||
<p className="text-gray-700 dark:text-gray-300 mb-4">
|
||||
Notre équipe d'experts vous accompagne dans l'intégration et le développement de vos solutions distribuées basées sur nos composants open source.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<a href="https://git.4nkweb.com" target="_blank" rel="noopener noreferrer">
|
||||
<Button className="bg-purple-600 hover:bg-purple-700">
|
||||
<Button className="bg-purple-600 dark:bg-purple-700 hover:bg-purple-700 dark:hover:bg-purple-600">
|
||||
<Code className="h-4 w-4 mr-2" />
|
||||
Accéder au code source
|
||||
</Button>
|
||||
</a>
|
||||
<Link href="/contact">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-purple-300 text-purple-700 hover:bg-purple-50 bg-transparent"
|
||||
>
|
||||
<Button variant="outline" className="border-purple-300 text-purple-700 dark:text-purple-300 hover:bg-purple-50 dark:hover:bg-purple-800 bg-transparent">
|
||||
Contactez-nous pour un projet
|
||||
</Button>
|
||||
</Link>
|
||||
@ -359,9 +291,8 @@ export default function HomePage() {
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600">
|
||||
<strong>Licence :</strong> Solutions disponibles sous licence open source permissive. Support
|
||||
commercial et accompagnement disponibles.
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
<strong>Licence :</strong> Solutions disponibles sous licence open source permissive. Support commercial et accompagnement disponibles.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@ -371,92 +302,97 @@ export default function HomePage() {
|
||||
</section>
|
||||
|
||||
{/* Summary */}
|
||||
<section className="py-16 px-4 bg-gradient-to-r from-blue-600 to-indigo-700 text-white">
|
||||
<section className="py-16 px-4 bg-gradient-to-r from-blue-600 to-indigo-700 dark:from-blue-800 dark:to-indigo-900 text-white transition-colors">
|
||||
<div className="container mx-auto text-center">
|
||||
<h2 className="text-4xl font-bold mb-8">🔐 En résumé</h2>
|
||||
<p className="text-xl mb-8 max-w-4xl mx-auto">
|
||||
DocV transforme la GED : plus simple, plus sûre, plus souveraine, et parfaitement compatible avec vos outils
|
||||
existants.
|
||||
DocV transforme la GED : plus simple, plus sûre, plus souveraine, et parfaitement compatible avec vos outils existants.
|
||||
</p>
|
||||
<p className="text-lg mb-8">
|
||||
C'est l'identité numérique que vous contrôlez, qui vous protège, et qui vous suit dans tous vos usages
|
||||
documentaires
|
||||
C'est l'identité numérique que vous contrôlez, qui vous protège, et qui vous suit dans tous vos usages documentaires
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Pricing */}
|
||||
<section id="tarifs" className="py-16 px-4 bg-white">
|
||||
<section id="tarifs" className="py-16 px-4 bg-white dark:bg-gray-900 transition-colors">
|
||||
<div className="container mx-auto">
|
||||
<h2 className="text-4xl font-bold text-center mb-12 text-gray-900">Tarification simple et universelle</h2>
|
||||
<h2 className="text-4xl font-bold text-center mb-12 text-gray-900 dark:text-white">
|
||||
Tarification simple et universelle
|
||||
</h2>
|
||||
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<Card className="border-2 border-blue-200 bg-blue-50">
|
||||
<Card className="border-2 border-blue-200 bg-blue-50 dark:bg-blue-900 dark:border-blue-700 transition-colors">
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle className="text-3xl font-bold text-blue-700">Offre Découverte</CardTitle>
|
||||
<CardDescription className="text-2xl font-semibold text-blue-600">2990 € HT / mois</CardDescription>
|
||||
<Badge className="bg-green-600 text-white text-lg px-4 py-2 mt-2">1000 jetons inclus</Badge>
|
||||
<CardTitle className="text-3xl font-bold text-blue-700 dark:text-blue-400">Offre Découverte</CardTitle>
|
||||
<CardDescription className="text-2xl font-semibold text-blue-600 dark:text-blue-300">
|
||||
2990 € HT / mois
|
||||
</CardDescription>
|
||||
<Badge className="bg-green-600 text-white text-lg px-4 py-2 mt-2 dark:bg-green-500">
|
||||
1000 jetons inclus
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<div className="bg-white p-6 rounded-lg mb-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4 text-center">🎯 Que comprennent 1000 jetons ?</h3>
|
||||
{/* Jetons détaillés */}
|
||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg mb-6 transition-colors">
|
||||
<h3 className="text-xl font-bold text-gray-900 dark:text-white mb-4 text-center">
|
||||
🎯 Que comprennent 1000 jetons ?
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="text-center p-4 bg-blue-50 rounded-lg">
|
||||
<Database className="h-8 w-8 mx-auto text-blue-600 mb-2" />
|
||||
<h4 className="font-semibold text-blue-800">Stockage permanent</h4>
|
||||
<p className="text-2xl font-bold text-blue-600">1 To</p>
|
||||
<p className="text-sm text-blue-700">Documents chiffrés et sécurisés</p>
|
||||
<div className="text-center p-4 bg-blue-50 dark:bg-blue-800 rounded-lg transition-colors">
|
||||
<Database className="h-8 w-8 mx-auto text-blue-600 dark:text-blue-300 mb-2" />
|
||||
<h4 className="font-semibold text-blue-800 dark:text-blue-200">Stockage permanent</h4>
|
||||
<p className="text-2xl font-bold text-blue-600 dark:text-blue-300">1 To</p>
|
||||
<p className="text-sm text-blue-700 dark:text-blue-200">Documents chiffrés et sécurisés</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-green-50 rounded-lg">
|
||||
<Zap className="h-8 w-8 mx-auto text-green-600 mb-2" />
|
||||
<h4 className="font-semibold text-green-800">Stockage temporaire</h4>
|
||||
<p className="text-2xl font-bold text-green-600">100 Go</p>
|
||||
<p className="text-sm text-green-700">Traitement IA et OCR</p>
|
||||
<div className="text-center p-4 bg-green-50 dark:bg-green-800 rounded-lg transition-colors">
|
||||
<Zap className="h-8 w-8 mx-auto text-green-600 dark:text-green-300 mb-2" />
|
||||
<h4 className="font-semibold text-green-800 dark:text-green-200">Stockage temporaire</h4>
|
||||
<p className="text-2xl font-bold text-green-600 dark:text-green-300">100 Go</p>
|
||||
<p className="text-sm text-green-700 dark:text-green-200">Traitement IA et OCR</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-purple-50 rounded-lg">
|
||||
<Users className="h-8 w-8 mx-auto text-purple-600 mb-2" />
|
||||
<h4 className="font-semibold text-purple-800">Nouveaux dossiers</h4>
|
||||
<p className="text-2xl font-bold text-purple-600">75</p>
|
||||
<p className="text-sm text-purple-700">Par mois maximum</p>
|
||||
<div className="text-center p-4 bg-purple-50 dark:bg-purple-800 rounded-lg transition-colors">
|
||||
<Users className="h-8 w-8 mx-auto text-purple-600 dark:text-purple-300 mb-2" />
|
||||
<h4 className="font-semibold text-purple-800 dark:text-purple-200">Nouveaux dossiers</h4>
|
||||
<p className="text-2xl font-bold text-purple-600 dark:text-purple-300">75</p>
|
||||
<p className="text-sm text-purple-700 dark:text-purple-200">Par mois maximum</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Architecture de stockage détaillée */}
|
||||
<div className="bg-gradient-to-r from-gray-50 to-blue-50 p-6 rounded-lg mb-6">
|
||||
<h4 className="font-semibold text-gray-800 mb-4 text-center">
|
||||
{/* Architecture */}
|
||||
<div className="bg-gradient-to-r from-gray-50 to-blue-50 dark:from-gray-800 dark:to-blue-900 p-6 rounded-lg mb-6 transition-colors">
|
||||
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-4 text-center">
|
||||
🏗️ Architecture de stockage souveraine
|
||||
</h4>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="bg-white p-4 rounded-lg border border-green-200">
|
||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg border border-green-200 dark:border-green-700 transition-colors">
|
||||
<div className="flex items-center space-x-2 mb-3">
|
||||
<Zap className="h-5 w-5 text-green-600" />
|
||||
<h5 className="font-semibold text-green-800">Stockage Temporaire</h5>
|
||||
<Zap className="h-5 w-5 text-green-600 dark:text-green-300" />
|
||||
<h5 className="font-semibold text-green-800 dark:text-green-200">Stockage Temporaire</h5>
|
||||
</div>
|
||||
<p className="text-sm text-gray-700 mb-2">
|
||||
<p className="text-sm text-gray-700 dark:text-gray-200 mb-2">
|
||||
<strong>Store chiffré local, distribué strictement en parties prenantes</strong>
|
||||
</p>
|
||||
<ul className="text-xs text-gray-600 space-y-1">
|
||||
<ul className="text-xs text-gray-600 dark:text-gray-300 space-y-1">
|
||||
<li>• Accès rapide pour modifications</li>
|
||||
<li>• Chiffrement bout en bout</li>
|
||||
<li>• Distribution contrôlée</li>
|
||||
<li>• Traitement IA local</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg border border-blue-200">
|
||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg border border-blue-200 dark:border-blue-700 transition-colors">
|
||||
<div className="flex items-center space-x-2 mb-3">
|
||||
<Database className="h-5 w-5 text-blue-600" />
|
||||
<h5 className="font-semibold text-blue-800">Stockage Permanent</h5>
|
||||
<Database className="h-5 w-5 text-blue-600 dark:text-blue-300" />
|
||||
<h5 className="font-semibold text-blue-800 dark:text-blue-200">Stockage Permanent</h5>
|
||||
</div>
|
||||
<p className="text-sm text-gray-700 mb-2">
|
||||
<p className="text-sm text-gray-700 dark:text-gray-200 mb-2">
|
||||
<strong>
|
||||
Store chiffré d'archivage local, distribué strictement en parties prenantes et sur un serveur
|
||||
de backup sans accès aux données compatible avec du cold storage
|
||||
Store chiffré d'archivage local, distribué strictement en parties prenantes et sur un serveur de backup
|
||||
</strong>
|
||||
</p>
|
||||
<ul className="text-xs text-gray-600 space-y-1">
|
||||
<ul className="text-xs text-gray-600 dark:text-gray-300 space-y-1">
|
||||
<li>• Conservation longue durée</li>
|
||||
<li>• Lecture seule sécurisée</li>
|
||||
<li>• Backup cold storage</li>
|
||||
@ -464,81 +400,80 @@ export default function HomePage() {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-3 bg-blue-100 rounded-lg">
|
||||
<p className="text-sm text-blue-800 text-center">
|
||||
<strong>🔐 Souveraineté totale :</strong> Vos données restent sous votre contrôle exclusif, même
|
||||
en backup
|
||||
<div className="mt-4 p-3 bg-blue-100 dark:bg-blue-800 rounded-lg transition-colors">
|
||||
<p className="text-sm text-blue-800 dark:text-blue-200 text-center">
|
||||
<strong>🔐 Souveraineté totale :</strong> Vos données restent sous votre contrôle exclusif, même en backup
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 mb-6">
|
||||
{/* Avantages */}
|
||||
<div className="space-y-3 mb-6 text-gray-900 dark:text-gray-200">
|
||||
<div className="flex items-center">
|
||||
<CheckCircle className="h-5 w-5 text-green-600 mr-3" />
|
||||
<CheckCircle className="h-5 w-5 text-green-600 dark:text-green-300 mr-3" />
|
||||
<span>Pas de coût par utilisateur</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<CheckCircle className="h-5 w-5 text-green-600 mr-3" />
|
||||
<CheckCircle className="h-5 w-5 text-green-600 dark:text-green-300 mr-3" />
|
||||
<span>Pas de surcoût pour l'IA embarquée</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<CheckCircle className="h-5 w-5 text-green-600 mr-3" />
|
||||
<CheckCircle className="h-5 w-5 text-green-600 dark:text-green-300 mr-3" />
|
||||
<span>Pas de frais de licence à la signature ou au document</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<CheckCircle className="h-5 w-5 text-green-600 mr-3" />
|
||||
<CheckCircle className="h-5 w-5 text-green-600 dark:text-green-300 mr-3" />
|
||||
<span>Pas de facturation par API ou par traitement</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Jetons supplémentaires */}
|
||||
<div className="bg-gradient-to-r from-orange-50 to-yellow-50 p-6 rounded-lg border border-orange-200 mb-6">
|
||||
<h4 className="font-semibold text-orange-800 mb-3 text-center">📦 Jetons supplémentaires</h4>
|
||||
<div className="bg-gradient-to-r from-orange-50 to-yellow-50 dark:from-orange-900 dark:to-yellow-800 p-6 rounded-lg border border-orange-200 dark:border-orange-700 mb-6 transition-colors">
|
||||
<h4 className="font-semibold text-orange-800 dark:text-orange-300 mb-3 text-center">📦 Jetons supplémentaires</h4>
|
||||
<div className="text-center mb-4">
|
||||
<p className="text-lg font-semibold text-orange-700">Lots de 250 jetons</p>
|
||||
<p className="text-2xl font-bold text-orange-600">+747,50 € HT/mois</p>
|
||||
<p className="text-sm text-orange-600">(2990 € ÷ 4 = 747,50 € par lot de 250 jetons)</p>
|
||||
<p className="text-lg font-semibold text-orange-700 dark:text-orange-200">Lots de 250 jetons</p>
|
||||
<p className="text-2xl font-bold text-orange-600 dark:text-orange-300">+747,50 € HT/mois</p>
|
||||
<p className="text-sm text-orange-600 dark:text-orange-200">(2990 € ÷ 4 = 747,50 € par lot de 250 jetons)</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-3 gap-4 text-center">
|
||||
<div className="bg-white p-3 rounded border border-orange-200">
|
||||
<p className="font-medium text-orange-800">+250 Go</p>
|
||||
<p className="text-xs text-orange-600">Stockage permanent</p>
|
||||
<div className="bg-white dark:bg-gray-800 p-3 rounded border border-orange-200 dark:border-orange-700">
|
||||
<p className="font-medium text-orange-800 dark:text-orange-300">+250 Go</p>
|
||||
<p className="text-xs text-orange-600 dark:text-orange-200">Stockage permanent</p>
|
||||
</div>
|
||||
<div className="bg-white p-3 rounded border border-orange-200">
|
||||
<p className="font-medium text-orange-800">+25 Go</p>
|
||||
<p className="text-xs text-orange-600">Stockage temporaire</p>
|
||||
<div className="bg-white dark:bg-gray-800 p-3 rounded border border-orange-200 dark:border-orange-700">
|
||||
<p className="font-medium text-orange-800 dark:text-orange-300">+25 Go</p>
|
||||
<p className="text-xs text-orange-600 dark:text-orange-200">Stockage temporaire</p>
|
||||
</div>
|
||||
<div className="bg-white p-3 rounded border border-orange-200">
|
||||
<p className="font-medium text-orange-800">+18 dossiers</p>
|
||||
<p className="text-xs text-orange-600">Nouveaux dossiers/mois</p>
|
||||
<div className="bg-white dark:bg-gray-800 p-3 rounded border border-orange-200 dark:border-orange-700">
|
||||
<p className="font-medium text-orange-800 dark:text-orange-300">+18 dossiers</p>
|
||||
<p className="text-xs text-orange-600 dark:text-orange-200">Nouveaux dossiers/mois</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-orange-600 mt-3 text-center font-medium">
|
||||
<p className="text-xs text-orange-600 dark:text-orange-200 mt-3 text-center font-medium">
|
||||
💡 Achetez uniquement ce dont vous avez besoin, quand vous en avez besoin
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Coût de setup */}
|
||||
<div className="bg-gradient-to-r from-gray-50 to-blue-50 p-6 rounded-lg border border-gray-200 mb-6">
|
||||
<h4 className="font-semibold text-gray-800 mb-2">⚙️ Coût de setup initial</h4>
|
||||
<p className="text-sm text-gray-700 mb-3">
|
||||
{/* Setup */}
|
||||
<div className="bg-gradient-to-r from-gray-50 to-blue-50 dark:from-gray-800 dark:to-blue-900 p-6 rounded-lg border border-gray-200 dark:border-gray-700 mb-6 transition-colors">
|
||||
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-2">⚙️ Coût de setup initial</h4>
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300 mb-3">
|
||||
Frais de mise en place unique, calculés selon vos contraintes spécifiques :
|
||||
</p>
|
||||
<ul className="text-sm text-gray-600 space-y-1">
|
||||
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
||||
<li>• Migration de données existantes</li>
|
||||
<li>• Intégrations systèmes tiers</li>
|
||||
<li>• Personnalisations interface</li>
|
||||
<li>• Formation équipes techniques</li>
|
||||
<li>• Accompagnement déploiement</li>
|
||||
</ul>
|
||||
<p className="text-xs text-gray-600 mt-2 font-medium">
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 mt-2 font-medium">
|
||||
💡 Devis personnalisé selon la complexité de votre environnement
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-lg font-semibold text-blue-700 mb-4">
|
||||
<p className="text-lg font-semibold text-blue-700 dark:text-blue-400 mb-4">
|
||||
Tarification à la consommation + setup personnalisé
|
||||
</p>
|
||||
<Link href="/contact">
|
||||
@ -553,8 +488,9 @@ export default function HomePage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-gray-900 text-white py-12 px-4">
|
||||
<footer className="bg-gray-900 dark:bg-gray-900 text-white py-12 px-4 transition-colors">
|
||||
<div className="container mx-auto">
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
@ -565,34 +501,36 @@ export default function HomePage() {
|
||||
By 4NK
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-gray-400 mb-4">
|
||||
<p className="text-gray-400 dark:text-gray-300 mb-4">
|
||||
4NK, pionnier du Web 5.0. Conçoit et développe des solutions de souveraineté.
|
||||
</p>
|
||||
<p className="text-gray-400">contact@docv.fr</p>
|
||||
<p className="text-gray-400 dark:text-gray-300">contact@docv.fr</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-4">Navigation</h3>
|
||||
<h3 className="text-lg font-semibold mb-4 text-white dark:text-white">Navigation</h3>
|
||||
<div className="space-y-2">
|
||||
<Link href="#produit" className="block text-gray-400 hover:text-white transition-colors">
|
||||
<Link href="#produit" className="block text-gray-400 dark:text-gray-300 hover:text-white transition-colors">
|
||||
Le produit
|
||||
</Link>
|
||||
<Link href="#securite" className="block text-gray-400 hover:text-white transition-colors">
|
||||
<Link href="#securite" className="block text-gray-400 dark:text-gray-300 hover:text-white transition-colors">
|
||||
Sécurité
|
||||
</Link>
|
||||
<Link href="#tarifs" className="block text-gray-400 hover:text-white transition-colors">
|
||||
<Link href="#tarifs" className="block text-gray-400 dark:text-gray-300 hover:text-white transition-colors">
|
||||
Tarifs
|
||||
</Link>
|
||||
<Link href="/formation" className="block text-gray-400 hover:text-white transition-colors">
|
||||
<Link href="/formation" className="block text-gray-400 dark:text-gray-300 hover:text-white transition-colors">
|
||||
Formation
|
||||
</Link>
|
||||
<Link href="/login" className="block text-gray-400 hover:text-white transition-colors">
|
||||
<Link href="" onClick={() => setShowAuthModal(true)} className="block text-gray-400 dark:text-gray-300 hover:text-white transition-colors">
|
||||
Connexion
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-gray-800 mt-8 pt-8 text-center text-gray-400">
|
||||
<p>© 2024 4NK. Tous droits réservés.</p>
|
||||
|
||||
<div className="border-t border-gray-800 dark:border-gray-700 mt-8 pt-8 text-center text-gray-400 dark:text-gray-300">
|
||||
<p>© 2025 4NK. Tous droits réservés.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -2,7 +2,7 @@ import { useState, useEffect, memo } from 'react';
|
||||
import Iframe from './Iframe';
|
||||
import MessageBus from '@/lib/4nk/MessageBus';
|
||||
import Loader from '@/lib/4nk/Loader';
|
||||
import Modal from '../ui/modal/Modal';
|
||||
import Modal from './Modal';
|
||||
|
||||
interface AuthModalProps {
|
||||
isOpen: boolean;
|
||||
|
||||
571
components/4nk/Chat.tsx
Normal file
571
components/4nk/Chat.tsx
Normal file
@ -0,0 +1,571 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import {
|
||||
MessageSquare,
|
||||
Search,
|
||||
Plus,
|
||||
Send,
|
||||
Paperclip,
|
||||
Smile,
|
||||
MoreHorizontal,
|
||||
Users,
|
||||
Circle,
|
||||
CheckCheck,
|
||||
Clock,
|
||||
File,
|
||||
Download,
|
||||
Brain,
|
||||
Shield,
|
||||
TrendingUp,
|
||||
CheckCircle,
|
||||
FileText,
|
||||
BarChart3,
|
||||
Zap,
|
||||
} from "lucide-react"
|
||||
import { useSearchParams } from "next/navigation"
|
||||
|
||||
interface ChatProps {
|
||||
heightClass?: string
|
||||
}
|
||||
|
||||
export default function Chat({ heightClass = "h-[calc(100vh-8rem)]" }: ChatProps) {
|
||||
const [selectedConversation, setSelectedConversation] = useState("global")
|
||||
const [newMessage, setNewMessage] = useState("")
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const userId = searchParams.get("user")
|
||||
const messageType = searchParams.get("message")
|
||||
const groupType = searchParams.get("type")
|
||||
|
||||
useEffect(() => {
|
||||
if (messageType === "new") {
|
||||
if (userId) {
|
||||
const messageData = sessionStorage.getItem("newMessage")
|
||||
if (messageData) {
|
||||
const data = JSON.parse(messageData)
|
||||
setSelectedConversation(userId)
|
||||
setNewMessage(`${data.subject ? `[${data.subject}] ` : ""}${data.content}`)
|
||||
sessionStorage.removeItem("newMessage")
|
||||
showNotification("info", `Conversation ouverte avec ${data.userName}`)
|
||||
}
|
||||
} else if (groupType === "group") {
|
||||
const groupData = sessionStorage.getItem("newGroupMessage")
|
||||
if (groupData) {
|
||||
const data = JSON.parse(groupData)
|
||||
setSelectedConversation("group-new")
|
||||
setNewMessage(`${data.subject ? `[${data.subject}] ` : ""}${data.content}`)
|
||||
sessionStorage.removeItem("newGroupMessage")
|
||||
showNotification("info", `Conversation de groupe créée avec ${data.users.length} utilisateur(s)`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [userId, messageType, groupType])
|
||||
|
||||
const showNotification = (type: "success" | "error" | "info", message: string) => {
|
||||
console.log(`${type.toUpperCase()}: ${message}`)
|
||||
}
|
||||
|
||||
const conversations = [
|
||||
{
|
||||
id: "global",
|
||||
name: "My Work",
|
||||
type: "group",
|
||||
avatar: "MW",
|
||||
lastMessage: "Bienvenue sur le chat de l’espace My Work",
|
||||
lastMessageTime: "Maintenant",
|
||||
unreadCount: 0,
|
||||
isOnline: false,
|
||||
isTyping: false,
|
||||
members: 0,
|
||||
},
|
||||
{
|
||||
id: "1",
|
||||
name: "Marie Dubois",
|
||||
type: "direct",
|
||||
avatar: "MD",
|
||||
lastMessage: "Parfait, merci pour la validation !",
|
||||
lastMessageTime: "14:32",
|
||||
unreadCount: 0,
|
||||
isOnline: true,
|
||||
isTyping: false,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Équipe Juridique",
|
||||
type: "group",
|
||||
avatar: "EJ",
|
||||
lastMessage: "IA DocV: Analyse terminée pour Contrat_Client_ABC.pdf",
|
||||
lastMessageTime: "13:45",
|
||||
unreadCount: 1,
|
||||
isOnline: false,
|
||||
isTyping: false,
|
||||
members: 5,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "Sophie Laurent",
|
||||
type: "direct",
|
||||
avatar: "SL",
|
||||
lastMessage: "Pouvez-vous m'envoyer le rapport ?",
|
||||
lastMessageTime: "12:20",
|
||||
unreadCount: 1,
|
||||
isOnline: false,
|
||||
isTyping: false,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "Direction",
|
||||
type: "group",
|
||||
avatar: "DIR",
|
||||
lastMessage: "Réunion reportée à demain 10h",
|
||||
lastMessageTime: "11:15",
|
||||
unreadCount: 0,
|
||||
isOnline: false,
|
||||
isTyping: false,
|
||||
members: 3,
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "Thomas Rousseau",
|
||||
type: "direct",
|
||||
avatar: "TR",
|
||||
lastMessage: "Merci pour l'info !",
|
||||
lastMessageTime: "Hier",
|
||||
unreadCount: 0,
|
||||
isOnline: true,
|
||||
isTyping: true,
|
||||
},
|
||||
]
|
||||
|
||||
const messages = [
|
||||
{
|
||||
id: "1",
|
||||
senderId: "marie",
|
||||
senderName: "Marie Dubois",
|
||||
content: "Bonjour ! J'ai besoin de votre avis sur le nouveau contrat client.",
|
||||
timestamp: "14:20",
|
||||
type: "text",
|
||||
status: "read",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
senderId: "me",
|
||||
senderName: "Moi",
|
||||
content: "Bien sûr, pouvez-vous me l'envoyer ?",
|
||||
timestamp: "14:22",
|
||||
type: "text",
|
||||
status: "read",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
senderId: "marie",
|
||||
senderName: "Marie Dubois",
|
||||
content: "",
|
||||
timestamp: "14:25",
|
||||
type: "file",
|
||||
fileName: "Contrat_Client_ABC.pdf",
|
||||
fileSize: "2.3 MB",
|
||||
status: "read",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
senderId: "me",
|
||||
senderName: "Moi",
|
||||
content:
|
||||
"J'ai relu le contrat, tout me semble correct. Les clauses de confidentialité sont bien définies.",
|
||||
timestamp: "14:30",
|
||||
type: "text",
|
||||
status: "read",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
senderId: "marie",
|
||||
senderName: "Marie Dubois",
|
||||
content: "Parfait, merci pour la validation !",
|
||||
timestamp: "14:32",
|
||||
type: "text",
|
||||
status: "delivered",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
senderId: "ai",
|
||||
senderName: "IA DocV",
|
||||
content: `📄 **Analyse IA du document "Contrat_Client_ABC.pdf"**
|
||||
|
||||
**Type de document :** PDF (2.3 MB)
|
||||
**Statut :** ✅ Validé
|
||||
**Dernière modification :** Il y a 2 heures
|
||||
|
||||
**📊 Analyse du contenu :**
|
||||
• Document juridique détecté avec haute précision
|
||||
• 3 tag(s) identifié(s) : contrat, client, juridique
|
||||
• Résumé automatique disponible
|
||||
• 47 pages analysées
|
||||
• 12 clauses contractuelles détectées
|
||||
|
||||
**🎯 Métriques de qualité :**
|
||||
• Lisibilité : 92%
|
||||
• Conformité juridique : 100%
|
||||
• Sécurité documentaire : Maximale
|
||||
• Complétude des informations : 95%
|
||||
|
||||
**🔍 Points clés identifiés :**
|
||||
• Durée du contrat : 12 mois
|
||||
• Montant total : 150 000€ HT
|
||||
• Clauses de confidentialité : ✅ Présentes et conformes
|
||||
• Propriété intellectuelle : ✅ Bien définie
|
||||
• Conditions de résiliation : ✅ Équilibrées
|
||||
|
||||
**🛡️ Analyse de conformité RGPD :**
|
||||
• Données personnelles : ⚠️ Détectées (coordonnées client)
|
||||
• Durée de conservation : Conforme (7 ans)
|
||||
• Droit à l'oubli : Applicable après expiration
|
||||
• Consentement : ✅ Explicite
|
||||
|
||||
**⚡ Recommandations :**
|
||||
• ✅ Document prêt pour signature
|
||||
• 📋 Archivage permanent recommandé
|
||||
• 🔄 Révision suggérée dans 11 mois
|
||||
• 📧 Notification client automatique activée
|
||||
|
||||
**📈 Score global : 94/100**
|
||||
|
||||
*Analyse générée automatiquement par l'IA DocV - Fiabilité : 98%*`,
|
||||
timestamp: "14:35",
|
||||
type: "ai_analysis",
|
||||
status: "delivered",
|
||||
analysisType: "document",
|
||||
documentName: "Contrat_Client_ABC.pdf",
|
||||
confidence: 98,
|
||||
processingTime: "2.3s",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
senderId: "ai",
|
||||
senderName: "IA DocV",
|
||||
content: `🔍 **Analyse comparative - Dossier Contrats**
|
||||
|
||||
**📊 Analyse de 8 documents similaires :**
|
||||
• Contrats clients : 5 documents
|
||||
• Avenants : 2 documents
|
||||
• Conditions générales : 1 document
|
||||
|
||||
**📈 Tendances identifiées :**
|
||||
• Montant moyen des contrats : +15% vs trimestre précédent
|
||||
• Durée moyenne : 14 mois (stable)
|
||||
• Taux de renouvellement : 87% (↗️ +5%)
|
||||
|
||||
**⚠️ Points d'attention :**
|
||||
• 2 contrats expirent dans les 30 jours
|
||||
• 1 clause de révision tarifaire à activer
|
||||
• Mise à jour RGPD requise sur 3 documents
|
||||
|
||||
**🎯 Actions recommandées :**
|
||||
1. Planifier renouvellement contrats Q1 2024
|
||||
2. Standardiser les clauses de confidentialité
|
||||
3. Créer un modèle basé sur ce contrat (performance optimale)
|
||||
|
||||
*Analyse prédictive activée - Prochaine révision : 15 février 2024*`,
|
||||
timestamp: "14:37",
|
||||
type: "ai_analysis",
|
||||
status: "delivered",
|
||||
analysisType: "comparative",
|
||||
confidence: 95,
|
||||
processingTime: "4.1s",
|
||||
},
|
||||
]
|
||||
|
||||
const filteredConversations = conversations
|
||||
|
||||
const currentConversation = conversations.find((conv) => conv.id === selectedConversation)
|
||||
|
||||
const handleSendMessage = () => {
|
||||
if (newMessage.trim()) {
|
||||
console.log("Sending message:", newMessage)
|
||||
setNewMessage("")
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case "sent":
|
||||
return <Clock className="h-3 w-3 text-gray-400" />
|
||||
case "delivered":
|
||||
return <CheckCheck className="h-3 w-3 text-gray-400" />
|
||||
case "read":
|
||||
return <CheckCheck className="h-3 w-3 text-blue-500" />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const getAnalysisIcon = (analysisType: string) => {
|
||||
switch (analysisType) {
|
||||
case "document":
|
||||
return <FileText className="h-4 w-4" />
|
||||
case "comparative":
|
||||
return <BarChart3 className="h-4 w-4" />
|
||||
case "security":
|
||||
return <Shield className="h-4 w-4" />
|
||||
case "performance":
|
||||
return <TrendingUp className="h-4 w-4" />
|
||||
default:
|
||||
return <Brain className="h-4 w-4" />
|
||||
}
|
||||
}
|
||||
|
||||
const renderAIMessage = (message: any) => {
|
||||
return (
|
||||
<div className="flex justify-start">
|
||||
<div className="max-w-4xl">
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<div className="w-8 h-8 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full flex items-center justify-center">
|
||||
<Brain className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">IA DocV</span>
|
||||
<Badge className="bg-gradient-to-r from-purple-100 to-blue-100 dark:from-purple-800 dark:to-blue-800 text-purple-700 dark:text-purple-200 border-purple-200 dark:border-purple-700 text-xs">
|
||||
{getAnalysisIcon(message.analysisType)}
|
||||
<span className="ml-1">
|
||||
{message.analysisType === "document"
|
||||
? "Analyse Document"
|
||||
: message.analysisType === "comparative"
|
||||
? "Analyse Comparative"
|
||||
: "Analyse IA"}
|
||||
</span>
|
||||
</Badge>
|
||||
{message.confidence && (
|
||||
<Badge className="bg-green-100 dark:bg-green-800 text-green-700 dark:text-green-200 border-green-200 dark:border-green-700 text-xs">
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
{message.confidence}% fiable
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-r from-purple-50 to-blue-50 dark:from-purple-900 dark:to-blue-900 border border-purple-200 dark:border-purple-700 rounded-lg p-4 shadow-sm">
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<div className="whitespace-pre-line text-gray-800 dark:text-gray-100 leading-relaxed">{message.content}</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-4 pt-3 border-t border-purple-200 dark:border-purple-700">
|
||||
<div className="flex items-center space-x-4 text-xs text-gray-600 dark:text-gray-400">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Zap className="h-3 w-3" />
|
||||
<span>Traité en {message.processingTime}</span>
|
||||
</div>
|
||||
{message.documentName && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<FileText className="h-3 w-3" />
|
||||
<span>{message.documentName}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-xs text-purple-600 dark:text-purple-400">{message.timestamp}</span>
|
||||
<div>{getStatusIcon(message.status)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${heightClass} flex`}>
|
||||
<div className="w-80 border-r bg-white dark:bg-gray-800 flex flex-col">
|
||||
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Messages</h2>
|
||||
<Button size="sm">
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{filteredConversations.map((conversation) => (
|
||||
<div
|
||||
key={conversation.id}
|
||||
onClick={() => setSelectedConversation(conversation.id)}
|
||||
className={`p-4 border-b cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 ${selectedConversation === conversation.id
|
||||
? "bg-blue-50 dark:bg-blue-900 border-r-2 border-blue-500 dark:border-blue-400"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="relative">
|
||||
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-800 rounded-full flex items-center justify-center">
|
||||
{conversation.type === "group" ? (
|
||||
<Users className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||
) : (
|
||||
<span className="text-blue-600 dark:text-blue-400 font-medium">{conversation.avatar}</span>
|
||||
)}
|
||||
</div>
|
||||
{conversation.isOnline && conversation.type === "direct" && (
|
||||
<Circle className="absolute -bottom-1 -right-1 h-4 w-4 text-green-500 fill-current" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-medium text-gray-900 dark:text-gray-100 truncate">{conversation.name}</h3>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">{conversation.lastMessageTime}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-1">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 truncate">
|
||||
{conversation.isTyping ? (
|
||||
<span className="text-blue-600 dark:text-blue-400 italic">En train d'écrire...</span>
|
||||
) : (
|
||||
<span
|
||||
className={conversation.lastMessage.includes("IA DocV:") ? "text-purple-600 dark:text-purple-400 font-medium" : ""}
|
||||
>
|
||||
{conversation.lastMessage}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
{conversation.unreadCount > 0 && (
|
||||
<Badge
|
||||
className={`text-white text-xs px-2 py-1 rounded-full ${conversation.lastMessage.includes("IA DocV:")
|
||||
? "bg-purple-600 dark:bg-purple-400"
|
||||
: "bg-blue-600 dark:bg-blue-400"
|
||||
}`}
|
||||
>
|
||||
{conversation.unreadCount}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{conversation.type === "group" && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">{conversation.members} membres</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col">
|
||||
{currentConversation ? (
|
||||
<>
|
||||
<div className="p-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative">
|
||||
<div className="w-10 h-10 bg-blue-100 dark:bg-blue-800 rounded-full flex items-center justify-center">
|
||||
{currentConversation.type === "group" ? (
|
||||
<Users className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||
) : (
|
||||
<span className="text-blue-600 dark:text-blue-400 font-medium">{currentConversation.avatar}</span>
|
||||
)}
|
||||
</div>
|
||||
{currentConversation.isOnline && currentConversation.type === "direct" && (
|
||||
<Circle className="absolute -bottom-1 -right-1 h-3 w-3 text-green-500 fill-current" />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900 dark:text-gray-100">{currentConversation.name}</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{currentConversation.type === "group"
|
||||
? `${currentConversation.members} membres`
|
||||
: currentConversation.isOnline
|
||||
? "En ligne"
|
||||
: "Hors ligne"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<MoreHorizontal className="h-4 w-4 text-gray-900 dark:text-gray-100" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50 dark:bg-gray-900">
|
||||
{messages.map((message) => (
|
||||
<div key={message.id}>
|
||||
{message.type === "ai_analysis" ? (
|
||||
renderAIMessage(message)
|
||||
) : (
|
||||
<div className={`flex ${message.senderId === "me" ? "justify-end" : "justify-start"}`}>
|
||||
<div
|
||||
className={`max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${message.senderId === "me"
|
||||
? "bg-blue-600 text-white"
|
||||
: "bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-sm"
|
||||
}`}
|
||||
>
|
||||
{message.type === "text" ? (
|
||||
<p className="text-sm">{message.content}</p>
|
||||
) : message.type === "file" ? (
|
||||
<div className="flex items-center space-x-3 p-2">
|
||||
<File className="h-8 w-8 text-gray-400 dark:text-gray-500" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">{message.fileName}</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{message.fileSize}</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
className={`flex items-center justify-between mt-1 ${message.senderId === "me" ? "text-blue-100" : "text-gray-500 dark:text-gray-400"
|
||||
}`}
|
||||
>
|
||||
<span className="text-xs">{message.timestamp}</span>
|
||||
{message.senderId === "me" && <div className="ml-2">{getStatusIcon(message.status)}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||
<div className="flex items-end space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<Paperclip className="h-4 w-4 text-gray-900 dark:text-gray-100" />
|
||||
</Button>
|
||||
<div className="flex-1">
|
||||
<Textarea
|
||||
placeholder="Tapez votre message..."
|
||||
value={newMessage}
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
}
|
||||
}}
|
||||
rows={1}
|
||||
className="resize-none bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 border border-gray-300 dark:border-gray-700"
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="sm">
|
||||
<Smile className="h-4 w-4 text-gray-900 dark:text-gray-100" />
|
||||
</Button>
|
||||
<Button onClick={handleSendMessage} disabled={!newMessage.trim()}>
|
||||
<Send className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||
<div className="text-center">
|
||||
<MessageSquare className="h-12 w-12 mx-auto text-gray-400 dark:text-gray-500 mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">Sélectionnez une conversation</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400">Choisissez une conversation pour commencer à discuter</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
218
components/4nk/FolderModal.tsx
Normal file
218
components/4nk/FolderModal.tsx
Normal file
@ -0,0 +1,218 @@
|
||||
import React, { useEffect, useState, memo } from 'react';
|
||||
import Modal from './Modal';
|
||||
import type { FolderData } from '@/lib/4nk/models/FolderData';
|
||||
|
||||
type FolderType = 'contrat' | 'projet' | 'rapport' | 'finance' | 'rh' | 'marketing' | 'autre';
|
||||
|
||||
interface FolderModalProps {
|
||||
folder?: FolderData;
|
||||
onSave?: (folderData: FolderData) => void;
|
||||
onCancel?: () => void;
|
||||
readOnly?: boolean;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
folderType?: FolderType;
|
||||
renderExtraFields?: (
|
||||
folderData: FolderData,
|
||||
setFolderData: React.Dispatch<React.SetStateAction<FolderData>>
|
||||
) => React.ReactNode;
|
||||
}
|
||||
|
||||
const defaultFolder: FolderData = {
|
||||
folderNumber: '',
|
||||
name: '',
|
||||
deedType: '',
|
||||
description: '',
|
||||
archived_description: '',
|
||||
status: 'active',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
customers: [],
|
||||
documents: [],
|
||||
notes: [],
|
||||
stakeholders: []
|
||||
};
|
||||
|
||||
function capitalize(s?: string) {
|
||||
if (!s) return '';
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
// Mapping des couleurs par type de dossier
|
||||
const folderColors: Record<FolderType, { bg: string; border: string; focus: string; button: string }> = {
|
||||
contrat: { bg: 'bg-blue-50 dark:bg-blue-900', border: 'border-blue-300 dark:border-blue-700', focus: 'focus:ring-blue-400 dark:focus:ring-blue-600', button: 'bg-blue-500 hover:bg-blue-600' },
|
||||
projet: { bg: 'bg-green-50 dark:bg-green-900', border: 'border-green-300 dark:border-green-700', focus: 'focus:ring-green-400 dark:focus:ring-green-600', button: 'bg-green-500 hover:bg-green-600' },
|
||||
rapport: { bg: 'bg-yellow-50 dark:bg-yellow-900', border: 'border-yellow-300 dark:border-yellow-700', focus: 'focus:ring-yellow-400 dark:focus:ring-yellow-600', button: 'bg-yellow-500 hover:bg-yellow-600' },
|
||||
finance: { bg: 'bg-indigo-50 dark:bg-indigo-900', border: 'border-indigo-300 dark:border-indigo-700', focus: 'focus:ring-indigo-400 dark:focus:ring-indigo-600', button: 'bg-indigo-500 hover:bg-indigo-600' },
|
||||
rh: { bg: 'bg-pink-50 dark:bg-pink-900', border: 'border-pink-300 dark:border-pink-700', focus: 'focus:ring-pink-400 dark:focus:ring-pink-600', button: 'bg-pink-500 hover:bg-pink-600' },
|
||||
marketing: { bg: 'bg-purple-50 dark:bg-purple-900', border: 'border-purple-300 dark:border-purple-700', focus: 'focus:ring-purple-400 dark:focus:ring-purple-600', button: 'bg-purple-500 hover:bg-purple-600' },
|
||||
autre: { bg: 'bg-gray-50 dark:bg-gray-800', border: 'border-gray-300 dark:border-gray-600', focus: 'focus:ring-blue-400 dark:focus:ring-blue-600', button: 'bg-blue-500 hover:bg-blue-600' },
|
||||
};
|
||||
|
||||
function FolderModal({
|
||||
folder = defaultFolder,
|
||||
onSave,
|
||||
onCancel,
|
||||
readOnly = false,
|
||||
isOpen,
|
||||
onClose,
|
||||
folderType = 'autre',
|
||||
renderExtraFields
|
||||
}: FolderModalProps) {
|
||||
const [folderData, setFolderData] = useState<FolderData>({ ...defaultFolder, ...folder });
|
||||
const [currentNote, setCurrentNote] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setFolderData({ ...defaultFolder, ...(folder || {}) });
|
||||
setCurrentNote('');
|
||||
}
|
||||
}, [isOpen, folder]);
|
||||
|
||||
const handleInputChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
||||
) => {
|
||||
const { name, value } = e.target;
|
||||
setFolderData(prev => ({ ...(prev as any), [name]: value } as FolderData));
|
||||
};
|
||||
|
||||
const addNote = () => {
|
||||
const v = currentNote.trim();
|
||||
if (!v) return;
|
||||
setFolderData(prev => ({ ...prev, notes: [...(prev.notes || []), v] }));
|
||||
setCurrentNote('');
|
||||
};
|
||||
|
||||
const removeNote = (note: string) => {
|
||||
setFolderData(prev => ({ ...prev, notes: (prev.notes || []).filter(n => n !== note) }));
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onSave?.({ ...folderData, updated_at: new Date().toISOString() });
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
if (onCancel) onCancel();
|
||||
else onClose();
|
||||
};
|
||||
|
||||
const colors = folderColors[folderType];
|
||||
const title = `Créer un dossier ${capitalize(folderType)}`;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={title} size="lg">
|
||||
<div className={`p-6 rounded-lg space-y-8 ${colors.bg} text-gray-900 dark:text-gray-100`}>
|
||||
<form className="space-y-8" onSubmit={handleSubmit}>
|
||||
{/* Informations principales */}
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Informations principales</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{['folderNumber', 'name'].map((field) => (
|
||||
<div className="relative" key={field}>
|
||||
<input
|
||||
type="text"
|
||||
name={field}
|
||||
value={folderData[field as keyof FolderData] || ''}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
disabled={readOnly}
|
||||
placeholder=" "
|
||||
className={`peer block w-full px-3 pt-5 pb-2 rounded-md ${colors.bg} text-gray-900 dark:text-gray-100 placeholder-transparent border ${colors.border} focus:outline-none ${colors.focus}`}
|
||||
/>
|
||||
<label className="absolute left-3 top-2.5 text-gray-500 dark:text-gray-400 text-sm transition-all peer-placeholder-shown:top-5 peer-placeholder-shown:text-base peer-focus:top-2.5 peer-focus:text-sm">
|
||||
{field === 'folderNumber' ? 'Numéro de dossier *' : 'Nom *'}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="relative">
|
||||
<textarea
|
||||
name="description"
|
||||
value={folderData.description || ''}
|
||||
onChange={handleInputChange}
|
||||
disabled={readOnly}
|
||||
placeholder=" "
|
||||
rows={3}
|
||||
className={`peer block w-full px-3 pt-5 pb-2 rounded-md ${colors.bg} text-gray-900 dark:text-gray-100 placeholder-transparent border ${colors.border} focus:outline-none ${colors.focus} resize-none`}
|
||||
/>
|
||||
<label className="absolute left-3 top-2.5 text-gray-500 dark:text-gray-400 text-sm transition-all peer-placeholder-shown:top-5 peer-placeholder-shown:text-base peer-focus:top-2.5 peer-focus:text-sm">
|
||||
Description
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Notes */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Notes</h3>
|
||||
<div className="space-y-2">
|
||||
{(folderData.notes || []).map((note, index) => (
|
||||
<div key={index} className="flex items-center justify-between bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-1 rounded-md">
|
||||
<span>{note}</span>
|
||||
{!readOnly && (
|
||||
<button type="button" className="text-red-500 hover:text-red-700 ml-2" onClick={() => removeNote(note)}>×</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{!readOnly && (
|
||||
<div className="flex space-x-2">
|
||||
<input
|
||||
type="text"
|
||||
value={currentNote}
|
||||
onChange={(e) => setCurrentNote(e.target.value)}
|
||||
placeholder="Ajouter une note"
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addNote(); } }}
|
||||
className={`flex-1 border rounded-md px-3 py-2 ${colors.bg} text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-400 border ${colors.border} focus:outline-none ${colors.focus}`}
|
||||
/>
|
||||
<button type="button" className={`px-4 py-2 text-white rounded-md ${colors.button}`} onClick={addNote}>Ajouter</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Informations système */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Informations système</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{['created_at', 'updated_at'].map((field) => {
|
||||
const value = new Date(folderData[field as keyof FolderData] as string).toLocaleString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
||||
const label = field === 'created_at' ? 'Créé le' : 'Dernière mise à jour';
|
||||
return (
|
||||
<div className="relative" key={field}>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
disabled
|
||||
readOnly
|
||||
placeholder=" "
|
||||
className={`peer block w-full px-3 pt-5 pb-2 rounded-md ${colors.bg} text-gray-900 dark:text-gray-100 placeholder-transparent border ${colors.border} focus:outline-none ${colors.focus}`}
|
||||
/>
|
||||
<label className="absolute left-3 top-2.5 text-gray-500 dark:text-gray-400 text-sm transition-all peer-placeholder-shown:top-5 peer-placeholder-shown:text-base peer-focus:top-2.5 peer-focus:text-sm">
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button type="button" className={`px-4 py-2 rounded-md ${colors.bg} text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:hover:bg-gray-700`} onClick={handleCancel}>Annuler</button>
|
||||
<button type="submit" className={`px-4 py-2 text-white rounded-md ${colors.button} disabled:opacity-50`} disabled={readOnly}>Enregistrer</button>
|
||||
</div>
|
||||
|
||||
{/* Champs spécifiques injectés */}
|
||||
{renderExtraFields && renderExtraFields(folderData, setFolderData)}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
FolderModal.displayName = 'FolderModal';
|
||||
export default memo(FolderModal);
|
||||
71
components/4nk/Modal.tsx
Normal file
71
components/4nk/Modal.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
}
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children, size = 'md' }) => {
|
||||
const [isVisible, setIsVisible] = useState(isOpen);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setIsVisible(true);
|
||||
} else {
|
||||
const timer = setTimeout(() => setIsVisible(false), 300); // correspond à la durée CSS
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
};
|
||||
|
||||
// Définir largeur modal selon taille
|
||||
const sizeClasses = {
|
||||
sm: 'max-w-md',
|
||||
md: 'max-w-xl',
|
||||
lg: 'max-w-3xl',
|
||||
xl: 'max-w-5xl',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm transition-opacity ${
|
||||
isOpen ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
onClick={handleBackdropClick}
|
||||
>
|
||||
<div
|
||||
className={`w-full ${sizeClasses[size]} bg-white dark:bg-gray-900 rounded-2xl shadow-2xl flex flex-col max-h-[90vh] overflow-hidden transform transition-all ${
|
||||
isOpen ? 'translate-y-0 opacity-100' : 'translate-y-6 opacity-0'
|
||||
}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-800">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">{title}</h2>
|
||||
<button
|
||||
className="flex items-center justify-center w-9 h-9 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white transition-all"
|
||||
onClick={onClose}
|
||||
aria-label="Fermer la modal"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto p-6 text-gray-900 dark:text-gray-100 bg-white dark:bg-gray-900">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
@ -31,8 +31,8 @@ const compareStates = (
|
||||
currentPrivateData?: Record<string, any>,
|
||||
previousPrivateData?: Record<string, any>
|
||||
) => {
|
||||
const result: Record<string, {
|
||||
value: any,
|
||||
const result: Record<string, {
|
||||
value: any,
|
||||
status: 'unchanged' | 'modified',
|
||||
hash?: string,
|
||||
isPrivate: boolean,
|
||||
@ -93,7 +93,7 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
|
||||
const [expandedBlocks, setExpandedBlocks] = useState<string[]>([]);
|
||||
const [isFiltered, setIsFiltered] = useState<boolean>(false);
|
||||
const [privateData, setPrivateData] = useState<Record<string, Record<string, any>>>({});
|
||||
const [editingField, setEditingField] = useState<{processId: string; stateId: string; key: string; value: any;} | null>(null);
|
||||
const [editingField, setEditingField] = useState<{ processId: string; stateId: string; key: string; value: any; } | null>(null);
|
||||
const [tempValue, setTempValue] = useState<any>(null);
|
||||
|
||||
const toggleBlock = (blockId: string) => {
|
||||
@ -180,7 +180,7 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
|
||||
if (isFileBlob(value)) {
|
||||
return (
|
||||
<div className="flex flex-col space-y-2" onClick={handleFormClick}>
|
||||
<input type="file" onChange={(e) => {
|
||||
<input className="dark:bg-gray-800 dark:text-gray-100" type="file" onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
@ -192,10 +192,10 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
}}/>
|
||||
}} />
|
||||
<div className="flex space-x-2">
|
||||
<button className="px-2 py-1 bg-blue-500 text-white rounded" onClick={() => { onSave(tempValue); setTempValue(null); }}>Sauvegarder</button>
|
||||
<button className="px-2 py-1 bg-gray-300 rounded" onClick={() => { onCancel(); setTempValue(null); }}>Annuler</button>
|
||||
<button className="px-2 py-1 bg-gray-300 dark:bg-gray-700 dark:text-gray-100 rounded" onClick={() => { onCancel(); setTempValue(null); }}>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -204,13 +204,13 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
|
||||
if (typeof value === 'boolean') {
|
||||
return (
|
||||
<div className="flex items-center space-x-2" onClick={handleFormClick}>
|
||||
<select className="border rounded px-2 py-1" value={tempValue.toString()} onChange={(e) => setTempValue(e.target.value === 'true')}>
|
||||
<select className="border rounded px-2 py-1 dark:bg-gray-800 dark:text-gray-100" value={tempValue.toString()} onChange={(e) => setTempValue(e.target.value === 'true')}>
|
||||
<option value="true">Vrai</option>
|
||||
<option value="false">Faux</option>
|
||||
</select>
|
||||
<div className="flex space-x-2">
|
||||
<button className="px-2 py-1 bg-blue-500 text-white rounded" onClick={() => onSave(tempValue)}>Sauvegarder</button>
|
||||
<button className="px-2 py-1 bg-gray-300 rounded" onClick={() => onCancel()}>Annuler</button>
|
||||
<button className="px-2 py-1 bg-gray-300 dark:bg-gray-700 dark:text-gray-100 rounded" onClick={() => onCancel()}>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -219,12 +219,17 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
|
||||
if (Array.isArray(value)) {
|
||||
return (
|
||||
<div className="flex flex-col space-y-2" onClick={handleFormClick}>
|
||||
<textarea className="border rounded p-2" rows={4} value={JSON.stringify(tempValue, null, 2)} onChange={(e) => {
|
||||
try { const parsed = JSON.parse(e.target.value); if (Array.isArray(parsed)) setTempValue(parsed); } catch {}
|
||||
}}/>
|
||||
<textarea
|
||||
className="border rounded p-2 dark:bg-gray-800 dark:text-gray-100"
|
||||
rows={4}
|
||||
value={JSON.stringify(tempValue, null, 2)}
|
||||
onChange={(e) => {
|
||||
try { const parsed = JSON.parse(e.target.value); if (Array.isArray(parsed)) setTempValue(parsed); } catch { }
|
||||
}}
|
||||
/>
|
||||
<div className="flex space-x-2">
|
||||
<button className="px-2 py-1 bg-blue-500 text-white rounded" onClick={() => onSave(tempValue)}>Sauvegarder</button>
|
||||
<button className="px-2 py-1 bg-gray-300 rounded" onClick={() => onCancel()}>Annuler</button>
|
||||
<button className="px-2 py-1 bg-gray-300 dark:bg-gray-700 dark:text-gray-100 rounded" onClick={() => onCancel()}>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -232,10 +237,16 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
|
||||
|
||||
return (
|
||||
<div className="flex space-x-2 items-center" onClick={handleFormClick}>
|
||||
<input className="border rounded px-2 py-1" type={typeof value === 'number' ? 'number' : 'text'} value={tempValue} onChange={(e) => setTempValue(typeof value === 'number' ? parseFloat(e.target.value) : e.target.value)} autoFocus />
|
||||
<input
|
||||
className="border rounded px-2 py-1 dark:bg-gray-800 dark:text-gray-100"
|
||||
type={typeof value === 'number' ? 'number' : 'text'}
|
||||
value={tempValue}
|
||||
onChange={(e) => setTempValue(typeof value === 'number' ? parseFloat(e.target.value) : e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
<div className="flex space-x-2">
|
||||
<button className="px-2 py-1 bg-blue-500 text-white rounded" onClick={() => onSave(tempValue)}>Sauvegarder</button>
|
||||
<button className="px-2 py-1 bg-gray-300 rounded" onClick={() => onCancel()}>Annuler</button>
|
||||
<button className="px-2 py-1 bg-gray-300 dark:bg-gray-700 dark:text-gray-100 rounded" onClick={() => onCancel()}>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -244,7 +255,11 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
|
||||
const renderDataField = (key: string, value: any, hash: string | undefined, isPrivate: boolean, processId: string, stateId: string, status: 'unchanged' | 'modified' = 'unchanged', originStateId?: string) => {
|
||||
const isEditing = editingField?.key === key && editingField?.processId === processId && editingField?.stateId === stateId;
|
||||
return (
|
||||
<div key={key} className={`border rounded p-2 mb-2 transition-colors ${status === 'modified' ? 'bg-green-100' : 'bg-white'}`}>
|
||||
<div
|
||||
key={key}
|
||||
className={`border rounded p-2 mb-2 transition-colors
|
||||
${status === 'modified' ? 'bg-green-100 dark:bg-green-900' : 'bg-white dark:bg-gray-800'}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center space-x-1">
|
||||
<span title={isPrivate ? 'Donnée privée' : 'Donnée publique'}>{isPrivate ? '🔒' : '🌐'}</span>
|
||||
@ -252,7 +267,10 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
|
||||
<span className="font-medium">{key}</span>
|
||||
{originStateId && originStateId !== stateId && <span title={`Propagé depuis l'état ${originStateId}`}>↺</span>}
|
||||
</div>
|
||||
<button className="text-sm text-gray-500 hover:text-gray-700" onClick={(e) => { e.stopPropagation(); setEditingField({ processId, stateId, key, value }); }}>
|
||||
<button
|
||||
className="text-sm text-gray-500 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-100"
|
||||
onClick={(e) => { e.stopPropagation(); setEditingField({ processId, stateId, key, value }); }}
|
||||
>
|
||||
{isEditing ? '✕' : '🔄'}
|
||||
</button>
|
||||
</div>
|
||||
@ -269,12 +287,16 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-full overflow-auto p-2">
|
||||
<div className="w-full h-full overflow-auto p-2 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-semibold">Processus</h2>
|
||||
<button className="px-2 py-1 border rounded text-sm" onClick={handleFilterClick}>{isFiltered ? 'Show All' : 'Filter'}</button>
|
||||
<button className="px-2 py-1 border rounded text-sm dark:border-gray-700 dark:text-gray-200" onClick={handleFilterClick}>
|
||||
{isFiltered ? 'Show All' : 'Filter'}
|
||||
</button>
|
||||
</div>
|
||||
<p className="mb-2 text-sm text-gray-500">{isFiltered ? Object.keys(processes).filter(p => myProcesses.includes(p)).length : Object.keys(processes).length} processus disponible(s)</p>
|
||||
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
{isFiltered ? Object.keys(processes).filter(p => myProcesses.includes(p)).length : Object.keys(processes).length} processus disponible(s)
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
{Object.entries(processes).map(([processId, process]) => {
|
||||
if (isFiltered && !myProcesses.includes(processId)) return null;
|
||||
@ -282,31 +304,40 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
|
||||
const stateCount = process.states.length - 1;
|
||||
|
||||
return (
|
||||
<div key={processId} className="border rounded shadow-sm">
|
||||
<div className="flex justify-between items-center p-2 cursor-pointer bg-gray-50" onClick={() => toggleBlock(processId)}>
|
||||
<div className="font-mono">{processId.slice(0,8)}...{processId.slice(-4)}</div>
|
||||
<div key={processId} className="border rounded shadow-sm border-gray-200 dark:border-gray-700">
|
||||
<div
|
||||
className="flex justify-between items-center p-2 cursor-pointer bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
onClick={() => toggleBlock(processId)}
|
||||
>
|
||||
<div className="font-mono">{processId.slice(0, 8)}...{processId.slice(-4)}</div>
|
||||
<div>{stateCount} état(s)</div>
|
||||
<div>{isExpanded ? '▼' : '▶'}</div>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="p-2 space-y-2 bg-white">
|
||||
<div className="p-2 space-y-2 bg-white dark:bg-gray-900">
|
||||
<div><strong>Process ID:</strong> {processId}</div>
|
||||
{process.states.map((state, index) => {
|
||||
if (index === stateCount) return null;
|
||||
if (myProcesses.includes(processId) && !privateData[state.state_id]) setTimeout(() => fetchPrivateData(processId, state.state_id), 0);
|
||||
const statePrivateData = privateData[state.state_id] || {};
|
||||
const stateData = compareStates(state, index, index > 0 ? process.states[index-1] : undefined, statePrivateData, index > 0 ? privateData[process.states[index-1].state_id] : undefined);
|
||||
const stateData = compareStates(state, index, index > 0 ? process.states[index - 1] : undefined, statePrivateData, index > 0 ? privateData[process.states[index - 1].state_id] : undefined);
|
||||
|
||||
return (
|
||||
<div key={`${processId}-state-${index}`} className="border-t pt-2">
|
||||
<h4 className="font-medium mb-1">État {index+1}</h4>
|
||||
<div key={`${processId}-state-${index}`} className="border-t border-gray-200 dark:border-gray-700 pt-2">
|
||||
<h4 className="font-medium mb-1">État {index + 1}</h4>
|
||||
<div className="text-sm mb-1"><strong>TransactionId:</strong> {state.commited_in}</div>
|
||||
<div className="text-sm mb-2"><strong>Empreinte totale de l'état:</strong> {state.state_id}</div>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(stateData).map(([key, { value, status, hash, isPrivate, stateId }]) => renderDataField(key, value, hash, isPrivate, processId, stateId, status, state.state_id))}
|
||||
{myProcesses.includes(processId) && Object.keys(statePrivateData).length === 0 && <div className="text-gray-400 text-sm">Chargement des données privées...</div>}
|
||||
{!myProcesses.includes(processId) && <div className="text-gray-400 text-sm">🔒 Vous n'avez pas accès aux données privées de ce processus</div>}
|
||||
{Object.entries(stateData).map(([key, { value, status, hash, isPrivate, stateId }]) =>
|
||||
renderDataField(key, value, hash, isPrivate, processId, stateId, status, state.state_id)
|
||||
)}
|
||||
{myProcesses.includes(processId) && Object.keys(statePrivateData).length === 0 && (
|
||||
<div className="text-gray-400 dark:text-gray-500 text-sm">Chargement des données privées...</div>
|
||||
)}
|
||||
{!myProcesses.includes(processId) && (
|
||||
<div className="text-gray-400 dark:text-gray-500 text-sm">🔒 Vous n'avez pas accès aux données privées de ce processus</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -1,347 +0,0 @@
|
||||
/* Container */
|
||||
.folder-container {
|
||||
padding: 1.5rem;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
/* Form */
|
||||
.folder-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.form-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
margin: 0;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid #e5e7eb;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.form-field label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #dc2626;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
/* Inputs */
|
||||
.form-field input,
|
||||
.form-field textarea,
|
||||
.form-field select {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.form-field input:focus,
|
||||
.form-field textarea:focus,
|
||||
.form-field select:focus {
|
||||
outline: none;
|
||||
border-color: #2563eb;
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
|
||||
}
|
||||
|
||||
.form-field input:disabled,
|
||||
.form-field textarea:disabled,
|
||||
.form-field select:disabled {
|
||||
background-color: #f3f4f6;
|
||||
color: #6b7280;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.form-field input::placeholder,
|
||||
.form-field textarea::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
.tag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
background-color: #f0f9ff;
|
||||
color: #0369a1;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.85rem;
|
||||
border: 1px solid #bae6fd;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tag-remove {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
border: none;
|
||||
background: none;
|
||||
color: #0369a1;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.tag-remove:hover {
|
||||
background-color: #e0f2fe;
|
||||
}
|
||||
|
||||
.tag-input-container {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.tag-input-container input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-add-tag {
|
||||
padding: 0.6rem 1rem;
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-add-tag:hover {
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.75rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-submit {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
/* Cancel button */
|
||||
.btn-cancel {
|
||||
background-color: white;
|
||||
color: #374151;
|
||||
border: 1px solid #d1d5db;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: #f9fafb;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Submit button */
|
||||
.btn-submit {
|
||||
background-color: #10b981;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-submit:hover {
|
||||
background-color: #059669;
|
||||
}
|
||||
|
||||
.btn-submit:disabled {
|
||||
background-color: #9ca3af;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.folder-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tag-input-container {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
.folder-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.folder-container::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.folder-container::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.folder-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
/* --- Thèmes par type de dossier --- */
|
||||
|
||||
/* Contrats */
|
||||
.folder-contrat .section-title {
|
||||
border-bottom-color: #2563eb;
|
||||
}
|
||||
.folder-contrat .tag-item {
|
||||
background-color: #eff6ff;
|
||||
color: #2563eb;
|
||||
border-color: #bfdbfe;
|
||||
}
|
||||
.folder-contrat .btn-submit {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
.folder-contrat .btn-submit:hover {
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
|
||||
/* Projets */
|
||||
.folder-projet .section-title {
|
||||
border-bottom-color: #059669;
|
||||
}
|
||||
.folder-projet .tag-item {
|
||||
background-color: #ecfdf5;
|
||||
color: #059669;
|
||||
border-color: #a7f3d0;
|
||||
}
|
||||
.folder-projet .btn-submit {
|
||||
background-color: #059669;
|
||||
}
|
||||
.folder-projet .btn-submit:hover {
|
||||
background-color: #047857;
|
||||
}
|
||||
|
||||
/* Rapports */
|
||||
.folder-rapport .section-title {
|
||||
border-bottom-color: #7c3aed;
|
||||
}
|
||||
.folder-rapport .tag-item {
|
||||
background-color: #f5f3ff;
|
||||
color: #7c3aed;
|
||||
border-color: #ddd6fe;
|
||||
}
|
||||
.folder-rapport .btn-submit {
|
||||
background-color: #7c3aed;
|
||||
}
|
||||
.folder-rapport .btn-submit:hover {
|
||||
background-color: #6d28d9;
|
||||
}
|
||||
|
||||
/* Finance */
|
||||
.folder-finance .section-title {
|
||||
border-bottom-color: #d97706;
|
||||
}
|
||||
.folder-finance .tag-item {
|
||||
background-color: #fffbeb;
|
||||
color: #d97706;
|
||||
border-color: #fcd34d;
|
||||
}
|
||||
.folder-finance .btn-submit {
|
||||
background-color: #d97706;
|
||||
}
|
||||
.folder-finance .btn-submit:hover {
|
||||
background-color: #b45309;
|
||||
}
|
||||
|
||||
/* Ressources Humaines */
|
||||
.folder-rh .section-title {
|
||||
border-bottom-color: #db2777;
|
||||
}
|
||||
.folder-rh .tag-item {
|
||||
background-color: #fdf2f8;
|
||||
color: #db2777;
|
||||
border-color: #f9a8d4;
|
||||
}
|
||||
.folder-rh .btn-submit {
|
||||
background-color: #db2777;
|
||||
}
|
||||
.folder-rh .btn-submit:hover {
|
||||
background-color: #be185d;
|
||||
}
|
||||
|
||||
/* Marketing */
|
||||
.folder-marketing .section-title {
|
||||
border-bottom-color: #4f46e5;
|
||||
}
|
||||
.folder-marketing .tag-item {
|
||||
background-color: #eef2ff;
|
||||
color: #4f46e5;
|
||||
border-color: #c7d2fe;
|
||||
}
|
||||
.folder-marketing .btn-submit {
|
||||
background-color: #4f46e5;
|
||||
}
|
||||
.folder-marketing .btn-submit:hover {
|
||||
background-color: #3730a3;
|
||||
}
|
||||
@ -1,325 +0,0 @@
|
||||
import React, { useEffect, useState, memo } from 'react';
|
||||
import Modal from './ui/modal/Modal';
|
||||
import './FolderModal.css';
|
||||
import type { FolderData } from '../lib/4nk/models/FolderData';
|
||||
|
||||
type FolderType = 'contrat' | 'projet' | 'rapport' | 'finance' | 'rh' | 'marketing' | 'autre';
|
||||
|
||||
interface FolderModalProps {
|
||||
folder?: FolderData;
|
||||
onSave?: (folderData: FolderData) => void;
|
||||
onCancel?: () => void;
|
||||
readOnly?: boolean;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
folderType?: FolderType;
|
||||
renderExtraFields?: (
|
||||
folderData: FolderData,
|
||||
setFolderData: React.Dispatch<React.SetStateAction<FolderData>>
|
||||
) => React.ReactNode;
|
||||
}
|
||||
|
||||
const defaultFolder: FolderData = {
|
||||
folderNumber: '',
|
||||
name: '',
|
||||
deedType: '',
|
||||
description: '',
|
||||
archived_description: '',
|
||||
status: 'active',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
customers: [],
|
||||
documents: [],
|
||||
notes: [],
|
||||
stakeholders: []
|
||||
};
|
||||
|
||||
function capitalize(s?: string) {
|
||||
if (!s) return '';
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
function FolderModal({
|
||||
folder = defaultFolder,
|
||||
onSave,
|
||||
onCancel,
|
||||
readOnly = false,
|
||||
isOpen,
|
||||
onClose,
|
||||
folderType = 'autre',
|
||||
renderExtraFields
|
||||
}: FolderModalProps) {
|
||||
const [folderData, setFolderData] = useState<FolderData>({ ...defaultFolder, ...folder });
|
||||
const [currentCustomer, setCurrentCustomer] = useState<string>('');
|
||||
const [currentStakeholder, setCurrentStakeholder] = useState<string>('');
|
||||
const [currentNote, setCurrentNote] = useState<string>('');
|
||||
|
||||
// Sync when modal opens or when folder prop changes (useful pour Edit)
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
// Merge with defaultFolder to ensure arrays exist
|
||||
setFolderData({ ...defaultFolder, ...(folder || {}) });
|
||||
setCurrentCustomer('');
|
||||
setCurrentStakeholder('');
|
||||
setCurrentNote('');
|
||||
}
|
||||
}, [isOpen, folder]);
|
||||
|
||||
// Generic input change handler
|
||||
const handleInputChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
||||
) => {
|
||||
const { name, value } = e.target;
|
||||
// cast to avoid TS complaints when updating dynamic fields
|
||||
setFolderData(prev => ({ ...(prev as any), [name]: value } as FolderData));
|
||||
};
|
||||
|
||||
/* ---------- Customers ---------- */
|
||||
const addCustomer = () => {
|
||||
const v = currentCustomer.trim();
|
||||
if (!v) return;
|
||||
if (!Array.isArray(folderData.customers)) folderData.customers = [];
|
||||
if (!folderData.customers.includes(v)) {
|
||||
setFolderData(prev => ({ ...prev, customers: [...(prev.customers || []), v] }));
|
||||
}
|
||||
setCurrentCustomer('');
|
||||
};
|
||||
|
||||
const removeCustomer = (customer: string) => {
|
||||
setFolderData(prev => ({ ...prev, customers: (prev.customers || []).filter(c => c !== customer) }));
|
||||
};
|
||||
|
||||
/* ---------- Stakeholders ---------- */
|
||||
const addStakeholder = () => {
|
||||
const v = currentStakeholder.trim();
|
||||
if (!v) return;
|
||||
if (!Array.isArray(folderData.stakeholders)) folderData.stakeholders = [];
|
||||
if (!folderData.stakeholders.includes(v)) {
|
||||
setFolderData(prev => ({ ...prev, stakeholders: [...(prev.stakeholders || []), v] }));
|
||||
}
|
||||
setCurrentStakeholder('');
|
||||
};
|
||||
|
||||
const removeStakeholder = (stakeholder: string) => {
|
||||
setFolderData(prev => ({ ...prev, stakeholders: (prev.stakeholders || []).filter(s => s !== stakeholder) }));
|
||||
};
|
||||
|
||||
/* ---------- Notes ---------- */
|
||||
const addNote = () => {
|
||||
const v = currentNote.trim();
|
||||
if (!v) return;
|
||||
if (!Array.isArray(folderData.notes)) folderData.notes = [];
|
||||
if (!folderData.notes.includes(v)) {
|
||||
setFolderData(prev => ({ ...prev, notes: [...(prev.notes || []), v] }));
|
||||
}
|
||||
setCurrentNote('');
|
||||
};
|
||||
|
||||
const removeNote = (note: string) => {
|
||||
setFolderData(prev => ({ ...prev, notes: (prev.notes || []).filter(n => n !== note) }));
|
||||
};
|
||||
|
||||
/* ---------- Submit / Cancel ---------- */
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (onSave) {
|
||||
onSave({
|
||||
...folderData,
|
||||
updated_at: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
if (onClose) {
|
||||
onClose(); // ← ça ferme le modal après sauvegarde
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
if (onCancel) {
|
||||
onCancel(); // ton callback spécifique
|
||||
} else if (onClose) {
|
||||
onClose(); // fallback si pas de onCancel
|
||||
}
|
||||
};
|
||||
|
||||
// Title text
|
||||
const title = `Créer un dossier ${capitalize(folderType)}`;
|
||||
|
||||
return (
|
||||
// On ne fait PAS "if (!isOpen) return null" : Modal gère l'animation/visibilité
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={title} size="lg">
|
||||
<div className={`folder-container folder-${folderType}`}>
|
||||
<form className="folder-form" onSubmit={handleSubmit}>
|
||||
|
||||
{/* Informations principales */}
|
||||
<div className="form-section">
|
||||
<h3 className="section-title">Informations principales</h3>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-field">
|
||||
<label>
|
||||
Numéro de dossier <span className="required">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="folderNumber"
|
||||
value={folderData.folderNumber || ''}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
disabled={readOnly}
|
||||
placeholder="ex: DOC-2025-001"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-field">
|
||||
<label>
|
||||
Nom <span className="required">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={folderData.name || ''}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
disabled={readOnly}
|
||||
placeholder="Nom du dossier"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-field">
|
||||
<label>Description</label>
|
||||
<textarea
|
||||
name="description"
|
||||
value={folderData.description || ''}
|
||||
onChange={handleInputChange}
|
||||
disabled={readOnly}
|
||||
placeholder="Description du dossier"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{folderData.status === 'archived' && (
|
||||
<div className="form-field">
|
||||
<label>Description d'archivage</label>
|
||||
<textarea
|
||||
name="archived_description"
|
||||
value={folderData.archived_description || ''}
|
||||
onChange={handleInputChange}
|
||||
disabled={readOnly}
|
||||
placeholder="Raison d'archivage"
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Champs spécifiques injectés */}
|
||||
{renderExtraFields && (
|
||||
<div className="form-section">
|
||||
{renderExtraFields(folderData, setFolderData)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Notes */}
|
||||
<div className="form-section">
|
||||
<h3 className="section-title">Notes</h3>
|
||||
|
||||
<div className="tag-list">
|
||||
{(folderData.notes || []).map((note, index) => (
|
||||
<div key={index} className="tag-item">
|
||||
<span>{note}</span>
|
||||
{!readOnly && (
|
||||
<button
|
||||
type="button"
|
||||
className="tag-remove"
|
||||
onClick={() => removeNote(note)}
|
||||
aria-label="Supprimer cette note"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!readOnly && (
|
||||
<div className="form-field tag-input-container">
|
||||
<input
|
||||
type="text"
|
||||
value={currentNote}
|
||||
onChange={(e) => setCurrentNote(e.target.value)}
|
||||
placeholder="Ajouter une note"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addNote();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-add-tag"
|
||||
onClick={addNote}
|
||||
>
|
||||
Ajouter
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Informations système */}
|
||||
<div className="form-section">
|
||||
<h3 className="section-title">Informations système</h3>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-field">
|
||||
<label>Créé le</label>
|
||||
<input
|
||||
type="text"
|
||||
value={new Date(folderData.created_at).toLocaleString('fr-FR', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
disabled
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-field">
|
||||
<label>Dernière mise à jour</label>
|
||||
<input
|
||||
type="text"
|
||||
value={new Date(folderData.updated_at).toLocaleString('fr-FR', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
disabled
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="form-actions">
|
||||
<button type="button" className="btn-cancel" onClick={handleCancel}>
|
||||
Annuler
|
||||
</button>
|
||||
<button type="submit" className="btn-submit" disabled={readOnly}>
|
||||
Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
FolderModal.displayName = 'FolderModal';
|
||||
export default memo(FolderModal);
|
||||
@ -1,101 +0,0 @@
|
||||
/* Overlay */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(17, 24, 39, 0.55);
|
||||
backdrop-filter: blur(6px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 1rem;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-in { opacity: 1; }
|
||||
.fade-out { opacity: 0; }
|
||||
|
||||
/* Container */
|
||||
.modal-container {
|
||||
background: #fff;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25);
|
||||
max-height: 90vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-sm { max-width: 28rem; }
|
||||
.modal-md { max-width: 32rem; }
|
||||
.modal-lg { max-width: 48rem; }
|
||||
.modal-xl { max-width: 64rem; }
|
||||
|
||||
.slide-in {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.slide-out {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1.25rem 1.5rem;
|
||||
background: linear-gradient(90deg, #f9fafb, #f3f4f6);
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Close Button */
|
||||
.modal-close-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 0.5rem;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.modal-close-button:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
color: #111827;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.modal-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
@keyframes fadeInSuccess {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
import './Modal.css';
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
}
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
children,
|
||||
size = 'md'
|
||||
}) => {
|
||||
const [isVisible, setIsVisible] = useState(isOpen);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setIsVisible(true);
|
||||
} else {
|
||||
// attendre que l'animation de fermeture se joue
|
||||
const timer = setTimeout(() => setIsVisible(false), 300); // durée en ms = durée CSS
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`modal-overlay ${isOpen ? 'fade-in' : 'fade-out'}`} onClick={handleBackdropClick}>
|
||||
<div className={`modal-container modal-${size} ${isOpen ? 'slide-in' : 'slide-out'}`}>
|
||||
<div className="modal-header">
|
||||
<h2 className="modal-title">{title}</h2>
|
||||
<button
|
||||
className="modal-close-button"
|
||||
onClick={onClose}
|
||||
aria-label="Fermer la modal"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-content">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
765
docs/API.md
765
docs/API.md
@ -1,765 +0,0 @@
|
||||
# Référence API - docv
|
||||
|
||||
Ce document est un modèle. Il doit être adapté par chaque projet dérivé. Il documente les APIs de l'infrastructure (RPC, HTTP, WebSocket) et doit rester cohérent avec la version publiée (`TEMPLATE_VERSION`) et le `CHANGELOG.md`.
|
||||
|
||||
## Vue d'Ensemble des APIs
|
||||
|
||||
L'infrastructure docv expose plusieurs interfaces pour différents types d'interactions :
|
||||
|
||||
- **Bitcoin Core RPC** : Interface JSON-RPC pour Bitcoin
|
||||
- **Blindbit HTTP** : API REST pour les paiements silencieux
|
||||
- **SDK Relay WebSocket** : Interface temps réel pour les clients
|
||||
- **SDK Relay HTTP** : API REST pour les opérations de gestion
|
||||
|
||||
## 1. API Bitcoin Core RPC
|
||||
|
||||
### Informations Générales
|
||||
|
||||
- **Protocole :** JSON-RPC
|
||||
- **Port :** 18443
|
||||
- **Authentification :** Cookie ou credentials
|
||||
- **Réseau :** Signet
|
||||
- **Base URL :** `http://localhost:18443`
|
||||
|
||||
### Authentification
|
||||
|
||||
#### Méthode Cookie (Recommandée)
|
||||
```bash
|
||||
# Le cookie est automatiquement utilisé par Bitcoin Core
|
||||
curl -X POST http://localhost:18443 \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"jsonrpc": "1.0", "id": "test", "method": "getblockchaininfo", "params": []}'
|
||||
```
|
||||
|
||||
#### Méthode Credentials
|
||||
```bash
|
||||
curl -X POST http://localhost:18443 \
|
||||
-H "Content-Type: application/json" \
|
||||
-u "username:password" \
|
||||
--data '{"jsonrpc": "1.0", "id": "test", "method": "getblockchaininfo", "params": []}'
|
||||
```
|
||||
|
||||
### Endpoints Principaux
|
||||
|
||||
#### getblockchaininfo
|
||||
Récupère les informations sur la blockchain.
|
||||
|
||||
**Requête :**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "1.0",
|
||||
"id": "test",
|
||||
"method": "getblockchaininfo",
|
||||
"params": []
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"chain": "signet",
|
||||
"blocks": 12345,
|
||||
"headers": 12345,
|
||||
"bestblockhash": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"difficulty": 1.0,
|
||||
"mediantime": 1234567890,
|
||||
"verificationprogress": 1.0,
|
||||
"initialblockdownload": false,
|
||||
"chainwork": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"size_on_disk": 123456789,
|
||||
"pruned": false,
|
||||
"pruneheight": null,
|
||||
"automatic_pruning": false,
|
||||
"prune_target_size": null,
|
||||
"warnings": ""
|
||||
},
|
||||
"error": null,
|
||||
"id": "test"
|
||||
}
|
||||
```
|
||||
|
||||
#### getblock
|
||||
Récupère les informations d'un bloc spécifique.
|
||||
|
||||
**Requête :**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "1.0",
|
||||
"id": "test",
|
||||
"method": "getblock",
|
||||
"params": ["blockhash", 2]
|
||||
}
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- `blockhash` : Hash du bloc
|
||||
- `verbosity` : Niveau de détail (0, 1, 2)
|
||||
|
||||
#### getrawtransaction
|
||||
Récupère une transaction brute.
|
||||
|
||||
**Requête :**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "1.0",
|
||||
"id": "test",
|
||||
"method": "getrawtransaction",
|
||||
"params": ["txid", true]
|
||||
}
|
||||
```
|
||||
|
||||
#### sendrawtransaction
|
||||
Envoie une transaction brute au réseau.
|
||||
|
||||
**Requête :**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "1.0",
|
||||
"id": "test",
|
||||
"method": "sendrawtransaction",
|
||||
"params": ["hexstring"]
|
||||
}
|
||||
```
|
||||
|
||||
#### getwalletinfo
|
||||
Récupère les informations du wallet.
|
||||
|
||||
**Requête :**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "1.0",
|
||||
"id": "test",
|
||||
"method": "getwalletinfo",
|
||||
"params": []
|
||||
}
|
||||
```
|
||||
|
||||
### Gestion des Erreurs
|
||||
|
||||
**Erreur typique :**
|
||||
```json
|
||||
{
|
||||
"result": null,
|
||||
"error": {
|
||||
"code": -32601,
|
||||
"message": "Method not found"
|
||||
},
|
||||
"id": "test"
|
||||
}
|
||||
```
|
||||
|
||||
**Codes d'erreur courants :**
|
||||
- `-32601` : Méthode non trouvée
|
||||
- `-32602` : Paramètres invalides
|
||||
- `-32603` : Erreur interne
|
||||
- `-1` : Erreur d'authentification
|
||||
|
||||
## 2. API Blindbit HTTP
|
||||
|
||||
### Informations Générales
|
||||
|
||||
- **Protocole :** HTTP REST
|
||||
- **Port :** 8000
|
||||
- **Base URL :** `http://localhost:8000`
|
||||
- **Content-Type :** `application/json`
|
||||
|
||||
### Endpoints
|
||||
|
||||
#### GET /health
|
||||
Vérifie la santé du service.
|
||||
|
||||
**Requête :**
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/health
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2024-12-19T14:30:00Z",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /generate-address
|
||||
Génère une adresse de paiement silencieux.
|
||||
|
||||
**Requête :**
|
||||
```json
|
||||
{
|
||||
"label": "payment_001",
|
||||
"amount": 0.001
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"address": "bc1p...",
|
||||
"label": "payment_001",
|
||||
"amount": 0.001,
|
||||
"created_at": "2024-12-19T14:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /payments
|
||||
Liste les paiements reçus.
|
||||
|
||||
**Requête :**
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/payments?limit=10&offset=0"
|
||||
```
|
||||
|
||||
**Paramètres de requête :**
|
||||
- `limit` : Nombre maximum de résultats (défaut: 10)
|
||||
- `offset` : Décalage pour la pagination (défaut: 0)
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"payments": [
|
||||
{
|
||||
"id": "payment_001",
|
||||
"address": "bc1p...",
|
||||
"amount": 0.001,
|
||||
"txid": "txid...",
|
||||
"block_height": 12345,
|
||||
"created_at": "2024-12-19T14:30:00Z"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"limit": 10,
|
||||
"offset": 0
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /payments/{id}
|
||||
Récupère les détails d'un paiement spécifique.
|
||||
|
||||
**Requête :**
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/payments/payment_001
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"id": "payment_001",
|
||||
"address": "bc1p...",
|
||||
"amount": 0.001,
|
||||
"txid": "txid...",
|
||||
"block_height": 12345,
|
||||
"confirmations": 6,
|
||||
"created_at": "2024-12-19T14:30:00Z",
|
||||
"status": "confirmed"
|
||||
}
|
||||
```
|
||||
|
||||
### Codes de Statut HTTP
|
||||
|
||||
- `200` : Succès
|
||||
- `201` : Créé
|
||||
- `400` : Requête invalide
|
||||
- `404` : Ressource non trouvée
|
||||
- `500` : Erreur serveur
|
||||
|
||||
## 3. API SDK Relay WebSocket
|
||||
|
||||
### Informations Générales
|
||||
|
||||
- **Protocole :** WebSocket/WSS
|
||||
- **Port :** 8090
|
||||
- **URL :** `ws://localhost:8090` ou `wss://localhost:8090`
|
||||
- **Format :** JSON
|
||||
|
||||
### Connexion
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://localhost:8090');
|
||||
|
||||
ws.onopen = function() {
|
||||
console.log('Connexion WebSocket établie');
|
||||
};
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
const message = JSON.parse(event.data);
|
||||
console.log('Message reçu:', message);
|
||||
};
|
||||
|
||||
ws.onerror = function(error) {
|
||||
console.error('Erreur WebSocket:', error);
|
||||
};
|
||||
|
||||
ws.onclose = function() {
|
||||
console.log('Connexion WebSocket fermée');
|
||||
};
|
||||
```
|
||||
|
||||
### Format des Messages
|
||||
|
||||
Tous les messages suivent le format JSON suivant :
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "message_type",
|
||||
"id": "unique_message_id",
|
||||
"timestamp": 1234567890,
|
||||
"data": {
|
||||
// Données spécifiques au type de message
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Types de Messages
|
||||
|
||||
#### Messages de Synchronisation
|
||||
|
||||
**StateSync :**
|
||||
```json
|
||||
{
|
||||
"type": "StateSync",
|
||||
"id": "state_001",
|
||||
"timestamp": 1234567890,
|
||||
"data": {
|
||||
"relay_id": "relay-1",
|
||||
"state": "running",
|
||||
"version": "1.0.0",
|
||||
"uptime": 3600
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**HealthSync :**
|
||||
```json
|
||||
{
|
||||
"type": "HealthSync",
|
||||
"id": "health_001",
|
||||
"timestamp": 1234567890,
|
||||
"data": {
|
||||
"relay_id": "relay-1",
|
||||
"status": "healthy",
|
||||
"uptime": 3600,
|
||||
"cpu_usage": 15.5,
|
||||
"memory_usage": 45.2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**MetricsSync :**
|
||||
```json
|
||||
{
|
||||
"type": "MetricsSync",
|
||||
"id": "metrics_001",
|
||||
"timestamp": 1234567890,
|
||||
"data": {
|
||||
"relay_id": "relay-1",
|
||||
"messages_sent": 1000,
|
||||
"messages_received": 950,
|
||||
"sync_errors": 5,
|
||||
"connected_relays": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Messages de Transaction
|
||||
|
||||
**TransactionReceived :**
|
||||
```json
|
||||
{
|
||||
"type": "TransactionReceived",
|
||||
"id": "tx_001",
|
||||
"timestamp": 1234567890,
|
||||
"data": {
|
||||
"txid": "txid...",
|
||||
"amount": 0.001,
|
||||
"address": "bc1p...",
|
||||
"block_height": 12345,
|
||||
"confirmations": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**BlockScanned :**
|
||||
```json
|
||||
{
|
||||
"type": "BlockScanned",
|
||||
"id": "block_001",
|
||||
"timestamp": 1234567890,
|
||||
"data": {
|
||||
"block_height": 12345,
|
||||
"block_hash": "hash...",
|
||||
"transactions_count": 150,
|
||||
"silent_payments_found": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Commandes Client
|
||||
|
||||
#### Ping
|
||||
```json
|
||||
{
|
||||
"type": "ping",
|
||||
"id": "ping_001",
|
||||
"timestamp": 1234567890,
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"type": "pong",
|
||||
"id": "ping_001",
|
||||
"timestamp": 1234567890,
|
||||
"data": {
|
||||
"relay_id": "relay-1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GetStatus
|
||||
```json
|
||||
{
|
||||
"type": "get_status",
|
||||
"id": "status_001",
|
||||
"timestamp": 1234567890,
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"type": "status",
|
||||
"id": "status_001",
|
||||
"timestamp": 1234567890,
|
||||
"data": {
|
||||
"relay_id": "relay-1",
|
||||
"status": "running",
|
||||
"uptime": 3600,
|
||||
"connected_relays": 3,
|
||||
"last_block_height": 12345
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. API SDK Relay HTTP
|
||||
|
||||
### Informations Générales
|
||||
|
||||
- **Protocole :** HTTP REST
|
||||
- **Port :** 8091
|
||||
- **Base URL :** `http://localhost:8091`
|
||||
- **Content-Type :** `application/json`
|
||||
|
||||
### Endpoints
|
||||
|
||||
#### GET /health
|
||||
Vérifie la santé du relais.
|
||||
|
||||
**Requête :**
|
||||
```bash
|
||||
curl -X GET http://localhost:8091/health
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"relay_id": "relay-1",
|
||||
"uptime": 3600,
|
||||
"version": "1.0.0",
|
||||
"connected_relays": 3
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /status
|
||||
Récupère le statut détaillé du relais.
|
||||
|
||||
**Requête :**
|
||||
```bash
|
||||
curl -X GET http://localhost:8091/status
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"relay_id": "relay-1",
|
||||
"status": "running",
|
||||
"uptime": 3600,
|
||||
"version": "1.0.0",
|
||||
"connected_relays": 3,
|
||||
"last_block_height": 12345,
|
||||
"sync_metrics": {
|
||||
"messages_sent": 1000,
|
||||
"messages_received": 950,
|
||||
"sync_errors": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /relays
|
||||
Liste les relais connectés.
|
||||
|
||||
**Requête :**
|
||||
```bash
|
||||
curl -X GET http://localhost:8091/relays
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"relays": [
|
||||
{
|
||||
"relay_id": "relay-2",
|
||||
"address": "sdk_relay_2:8090",
|
||||
"status": "connected",
|
||||
"connected_since": 1234567890,
|
||||
"last_heartbeat": 1234567890
|
||||
},
|
||||
{
|
||||
"relay_id": "relay-3",
|
||||
"address": "sdk_relay_3:8090",
|
||||
"status": "connected",
|
||||
"connected_since": 1234567890,
|
||||
"last_heartbeat": 1234567890
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /metrics
|
||||
Récupère les métriques de synchronisation.
|
||||
|
||||
**Requête :**
|
||||
```bash
|
||||
curl -X GET http://localhost:8091/metrics
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"messages_sent": 1000,
|
||||
"messages_received": 950,
|
||||
"sync_errors": 5,
|
||||
"last_sync_timestamp": 1234567890,
|
||||
"connected_relays": 3,
|
||||
"mesh_health": 0.95
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /sync
|
||||
Force une synchronisation manuelle.
|
||||
|
||||
**Requête :**
|
||||
```json
|
||||
{
|
||||
"sync_type": "StateSync",
|
||||
"target_relay": "relay-2"
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Synchronisation initiée",
|
||||
"sync_id": "sync_001"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Gestion des Erreurs
|
||||
|
||||
### Erreurs WebSocket
|
||||
|
||||
**Erreur de connexion :**
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"id": "error_001",
|
||||
"timestamp": 1234567890,
|
||||
"data": {
|
||||
"code": "CONNECTION_ERROR",
|
||||
"message": "Impossible de se connecter au relais",
|
||||
"details": "Connection refused"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Erreur de message :**
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"id": "error_002",
|
||||
"timestamp": 1234567890,
|
||||
"data": {
|
||||
"code": "INVALID_MESSAGE",
|
||||
"message": "Format de message invalide",
|
||||
"details": "Missing required field 'type'"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Erreurs HTTP
|
||||
|
||||
**Erreur 400 :**
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "INVALID_REQUEST",
|
||||
"message": "Requête invalide",
|
||||
"details": "Missing required parameter 'relay_id'"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Erreur 500 :**
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "INTERNAL_ERROR",
|
||||
"message": "Erreur interne du serveur",
|
||||
"details": "Database connection failed"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Exemples d'Utilisation
|
||||
|
||||
### Exemple Python - WebSocket
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
|
||||
async def connect_to_relay():
|
||||
uri = "ws://localhost:8090"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
# Envoyer un ping
|
||||
ping_message = {
|
||||
"type": "ping",
|
||||
"id": "ping_001",
|
||||
"timestamp": int(time.time()),
|
||||
"data": {}
|
||||
}
|
||||
await websocket.send(json.dumps(ping_message))
|
||||
|
||||
# Écouter les messages
|
||||
async for message in websocket:
|
||||
data = json.loads(message)
|
||||
print(f"Message reçu: {data}")
|
||||
|
||||
asyncio.run(connect_to_relay())
|
||||
```
|
||||
|
||||
### Exemple JavaScript - WebSocket
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://localhost:8090');
|
||||
|
||||
ws.onopen = function() {
|
||||
// Envoyer un ping
|
||||
const pingMessage = {
|
||||
type: 'ping',
|
||||
id: 'ping_001',
|
||||
timestamp: Date.now(),
|
||||
data: {}
|
||||
};
|
||||
ws.send(JSON.stringify(pingMessage));
|
||||
};
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
const message = JSON.parse(event.data);
|
||||
console.log('Message reçu:', message);
|
||||
};
|
||||
```
|
||||
|
||||
### Exemple cURL - Bitcoin Core RPC
|
||||
|
||||
```bash
|
||||
# Récupérer les informations de la blockchain
|
||||
curl -X POST http://localhost:18443 \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{
|
||||
"jsonrpc": "1.0",
|
||||
"id": "test",
|
||||
"method": "getblockchaininfo",
|
||||
"params": []
|
||||
}'
|
||||
|
||||
# Récupérer un bloc spécifique
|
||||
curl -X POST http://localhost:18443 \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{
|
||||
"jsonrpc": "1.0",
|
||||
"id": "test",
|
||||
"method": "getblock",
|
||||
"params": ["blockhash", 2]
|
||||
}'
|
||||
```
|
||||
|
||||
## 7. Limites et Quotas
|
||||
|
||||
### Bitcoin Core RPC
|
||||
- **Taux limite :** 1000 requêtes/minute par défaut
|
||||
- **Taille des requêtes :** 32MB maximum
|
||||
- **Connexions simultanées :** 125 par défaut
|
||||
|
||||
### Blindbit HTTP
|
||||
- **Taux limite :** 100 requêtes/minute
|
||||
- **Taille des requêtes :** 10MB maximum
|
||||
- **Connexions simultanées :** 50
|
||||
|
||||
### SDK Relay WebSocket
|
||||
- **Connexions simultanées :** 1000 par relais
|
||||
- **Taille des messages :** 1MB maximum
|
||||
- **Heartbeat :** 30 secondes
|
||||
|
||||
### SDK Relay HTTP
|
||||
- **Taux limite :** 200 requêtes/minute
|
||||
- **Taille des requêtes :** 5MB maximum
|
||||
- **Connexions simultanées :** 100
|
||||
|
||||
## 8. Sécurité
|
||||
|
||||
### Authentification
|
||||
- **Bitcoin Core :** Cookie d'authentification
|
||||
- **Blindbit :** À définir selon les besoins
|
||||
- **SDK Relay :** Authentification WebSocket (optionnelle)
|
||||
|
||||
### Chiffrement
|
||||
- **RPC Bitcoin :** HTTP (non chiffré en local)
|
||||
- **HTTP Blindbit :** HTTP (non chiffré en local)
|
||||
- **WebSocket SDK Relay :** WSS (chiffré)
|
||||
|
||||
### Bonnes Pratiques
|
||||
- Utiliser HTTPS/WSS en production
|
||||
- Implémenter l'authentification appropriée
|
||||
- Valider toutes les entrées
|
||||
- Limiter les taux de requêtes
|
||||
- Monitorer les accès
|
||||
|
||||
## 9. Monitoring et Observabilité
|
||||
|
||||
### Métriques à Surveiller
|
||||
- **Latence des APIs :** Temps de réponse
|
||||
- **Taux d'erreur :** Pourcentage d'erreurs
|
||||
- **Débit :** Requêtes par seconde
|
||||
- **Utilisation des ressources :** CPU, mémoire, réseau
|
||||
|
||||
### Logs
|
||||
- **Logs d'accès :** Requêtes et réponses
|
||||
- **Logs d'erreur :** Erreurs et exceptions
|
||||
- **Logs de performance :** Métriques de performance
|
||||
|
||||
### Alertes
|
||||
- **Erreurs 5xx :** Erreurs serveur
|
||||
- **Latence élevée :** Temps de réponse > 1s
|
||||
- **Taux d'erreur élevé :** > 5%
|
||||
- **Services indisponibles :** Health checks en échec
|
||||
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
# Architecture Technique - docv
|
||||
|
||||
|
||||
## Vue d'Ensemble de l'Architecture
|
||||
|
||||
Ce document sert de modèle générique. Il doit être adapté par chaque projet dérivé de ce template.
|
||||
|
||||
### Architecture Générale
|
||||
|
||||
Composants majeurs et couplages:
|
||||
- Bitcoin Core, Blindbit, Relais SDK, UI/clients
|
||||
- Réseau privé Docker, ZMQ, WebSocket
|
||||
- CI/CD Gitea Actions
|
||||
|
||||
## Composants Principaux
|
||||
|
||||
Listez ici les composants avec responsabilités, entrées/sorties et SLA.
|
||||
|
||||
### 1. Environnements
|
||||
|
||||
|
||||
### 2. Orchestration
|
||||
|
||||
### 3. CI/CD
|
||||
|
||||
- Gitea Actions avec jobs: qualité, tests, intégration, sécurité, docker-build, documentation, release-guard
|
||||
- Release Guard impose: tests, documentation, compilation, alignement `VERSION`/`TEMPLATE_VERSION` ↔ `CHANGELOG.md` ↔ tag, choix latest vs wip
|
||||
- Fichier version: `TEMPLATE_VERSION` (ou `VERSION`) est la source de vérité; `CHANGELOG.md` doit contenir l’entrée correspondante
|
||||
## Troubleshooting
|
||||
|
||||
### 1. Problèmes de Synchronisation
|
||||
|
||||
- **Connexions perdues :** Vérifier la connectivité réseau
|
||||
- **Messages dupliqués :** Vérifier le cache de déduplication
|
||||
- **Latence élevée :** Vérifier les ressources système
|
||||
|
||||
### 2. Problèmes de Performance
|
||||
|
||||
- **Utilisation mémoire :** Vérifier les fuites mémoire
|
||||
- **CPU élevé :** Vérifier les boucles infinies
|
||||
- **Disque plein :** Nettoyer les logs et données
|
||||
|
||||
### 3. Problèmes de Configuration
|
||||
|
||||
- **Ports bloqués :** Vérifier le pare-feu
|
||||
- **Volumes manquants :** Vérifier les permissions
|
||||
- **Variables d'environnement :** Vérifier la configuration
|
||||
|
||||
## Évolution Future
|
||||
|
||||
|
||||
@ -1,238 +0,0 @@
|
||||
# Automatisation SSH pour Push - ihm_client
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
L'automatisation SSH pour les push permet d'utiliser automatiquement votre clé SSH pour tous les push vers le repository `ihm_client` sur Gitea, sans avoir à spécifier manuellement les paramètres SSH.
|
||||
|
||||
## Configuration automatique
|
||||
|
||||
### 1. Configuration Git globale
|
||||
|
||||
La configuration SSH est automatiquement appliquée :
|
||||
|
||||
```bash
|
||||
git config --global url."git@git.4nkweb.com:".insteadOf "https://git.4nkweb.com/"
|
||||
```
|
||||
|
||||
### 2. Vérification SSH
|
||||
|
||||
Le script vérifie automatiquement la configuration SSH :
|
||||
|
||||
```bash
|
||||
ssh -T git@git.4nkweb.com
|
||||
```
|
||||
|
||||
## Scripts d'automatisation
|
||||
|
||||
### Script principal : `auto-ssh-push.sh`
|
||||
|
||||
Le script `scripts/auto-ssh-push.sh` offre plusieurs modes de push automatique :
|
||||
|
||||
#### Options disponibles
|
||||
|
||||
```bash
|
||||
# Push rapide (message automatique)
|
||||
./scripts/auto-ssh-push.sh quick
|
||||
|
||||
# Push avec message personnalisé
|
||||
./scripts/auto-ssh-push.sh message "feat: nouvelle fonctionnalité"
|
||||
|
||||
# Push sur une branche spécifique
|
||||
./scripts/auto-ssh-push.sh branch feature/nouvelle-fonctionnalite
|
||||
|
||||
# Push et préparation merge
|
||||
./scripts/auto-ssh-push.sh merge feature/nouvelle-fonctionnalite main
|
||||
|
||||
# Status et push conditionnel
|
||||
./scripts/auto-ssh-push.sh status
|
||||
```
|
||||
|
||||
#### Exemples d'utilisation
|
||||
|
||||
```bash
|
||||
# Push rapide sur la branche courante
|
||||
./scripts/auto-ssh-push.sh quick
|
||||
|
||||
# Push avec message de commit
|
||||
./scripts/auto-ssh-push.sh message "fix: correction du bug de synchronisation"
|
||||
|
||||
# Push sur une branche spécifique
|
||||
./scripts/auto-ssh-push.sh branch develop
|
||||
|
||||
# Push et création de Pull Request
|
||||
./scripts/auto-ssh-push.sh merge feature/nouvelle-fonctionnalite main
|
||||
```
|
||||
|
||||
### Alias Git globaux
|
||||
|
||||
Des alias Git ont été configurés pour simplifier les push :
|
||||
|
||||
```bash
|
||||
# Push avec message personnalisé
|
||||
git ssh-push "Mon message de commit"
|
||||
|
||||
# Push rapide (message automatique)
|
||||
git quick-push
|
||||
```
|
||||
|
||||
## Fonctionnalités automatiques
|
||||
|
||||
### 1. Configuration SSH automatique
|
||||
|
||||
- Configuration Git pour utiliser SSH
|
||||
- Vérification de l'authentification SSH
|
||||
- Gestion des erreurs de configuration
|
||||
|
||||
### 2. Push automatique
|
||||
|
||||
- Ajout automatique de tous les changements (`git add .`)
|
||||
- Commit automatique avec message
|
||||
- Push automatique vers la branche courante
|
||||
|
||||
### 3. Gestion des branches
|
||||
|
||||
- Détection automatique de la branche courante
|
||||
- Support des branches personnalisées
|
||||
- Préparation des Pull Requests
|
||||
|
||||
### 4. Validation et sécurité
|
||||
|
||||
- Vérification de l'authentification SSH avant push
|
||||
- Messages d'erreur explicites
|
||||
- Gestion des cas d'échec
|
||||
|
||||
## Workflow recommandé
|
||||
|
||||
### Développement quotidien
|
||||
|
||||
```bash
|
||||
# 1. Faire vos modifications
|
||||
# 2. Push rapide
|
||||
./scripts/auto-ssh-push.sh quick
|
||||
|
||||
# Ou avec message personnalisé
|
||||
./scripts/auto-ssh-push.sh message "feat: ajout de la fonctionnalité X"
|
||||
```
|
||||
|
||||
### Développement de fonctionnalités
|
||||
|
||||
```bash
|
||||
# 1. Créer une branche
|
||||
git checkout -b feature/nouvelle-fonctionnalite
|
||||
|
||||
# 2. Développer
|
||||
# 3. Push sur la branche
|
||||
./scripts/auto-ssh-push.sh branch feature/nouvelle-fonctionnalite
|
||||
|
||||
# 4. Préparer le merge
|
||||
./scripts/auto-ssh-push.sh merge feature/nouvelle-fonctionnalite main
|
||||
```
|
||||
|
||||
### Intégration continue
|
||||
|
||||
```bash
|
||||
# Push automatique après tests
|
||||
./scripts/auto-ssh-push.sh message "ci: tests passés, déploiement automatique"
|
||||
```
|
||||
|
||||
## Dépannage
|
||||
|
||||
### Problèmes courants
|
||||
|
||||
#### 1. Échec d'authentification SSH
|
||||
|
||||
```bash
|
||||
# Vérifier la clé SSH
|
||||
ssh -T git@git.4nkweb.com
|
||||
|
||||
# Si échec, configurer une nouvelle clé
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_4nk
|
||||
ssh-add ~/.ssh/id_ed25519_4nk
|
||||
```
|
||||
|
||||
#### 2. Configuration Git manquante
|
||||
|
||||
```bash
|
||||
# Reconfigurer Git pour SSH
|
||||
git config --global url."git@git.4nkweb.com:".insteadOf "https://git.4nkweb.com/"
|
||||
```
|
||||
|
||||
#### 3. Permissions de script
|
||||
|
||||
```bash
|
||||
# Rendre le script exécutable
|
||||
chmod +x scripts/auto-ssh-push.sh
|
||||
```
|
||||
|
||||
### Commandes de diagnostic
|
||||
|
||||
```bash
|
||||
# Vérifier la configuration SSH
|
||||
ssh -vT git@git.4nkweb.com
|
||||
|
||||
# Vérifier la configuration Git
|
||||
git config --global --list | grep url
|
||||
|
||||
# Vérifier les remotes
|
||||
git remote -v
|
||||
```
|
||||
|
||||
## Intégration avec CI/CD
|
||||
|
||||
### Workflow Gitea Actions
|
||||
|
||||
Le workflow CI/CD (`.gitea/workflows/ci.yml`) utilise automatiquement SSH :
|
||||
|
||||
```yaml
|
||||
- name: Setup SSH for Gitea
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -H git.4nkweb.com >> ~/.ssh/known_hosts
|
||||
git config --global url."git@git.4nkweb.com:".insteadOf "https://git.4nkweb.com/"
|
||||
```
|
||||
|
||||
### Variables d'environnement
|
||||
|
||||
- `SSH_PRIVATE_KEY` : Clé SSH privée pour l'authentification
|
||||
- `SSH_PUBLIC_KEY` : Clé SSH publique (optionnelle)
|
||||
|
||||
## Sécurité
|
||||
|
||||
### Bonnes pratiques
|
||||
|
||||
- Les clés SSH sont stockées de manière sécurisée
|
||||
- Les permissions des fichiers SSH sont correctement configurées
|
||||
- La vérification des hôtes SSH est activée
|
||||
- Les clés sont régulièrement renouvelées
|
||||
|
||||
### Permissions recommandées
|
||||
|
||||
```bash
|
||||
chmod 700 ~/.ssh
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
chmod 644 ~/.ssh/id_rsa.pub
|
||||
chmod 600 ~/.ssh/config
|
||||
```
|
||||
|
||||
## Évolution
|
||||
|
||||
### Améliorations futures
|
||||
|
||||
- Support pour plusieurs clés SSH
|
||||
- Rotation automatique des clés
|
||||
- Intégration avec un gestionnaire de secrets
|
||||
- Support pour l'authentification par certificats SSH
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Vérification régulière de la validité des clés SSH
|
||||
- Mise à jour des configurations selon les bonnes pratiques
|
||||
- Documentation des changements de configuration
|
||||
|
||||
## Conclusion
|
||||
|
||||
L'automatisation SSH pour les push simplifie considérablement le workflow de développement en éliminant la nécessité de configurer manuellement SSH pour chaque opération Git. Le script `auto-ssh-push.sh` et les alias Git offrent une interface simple et sécurisée pour tous les push vers le repository `ihm_client`.
|
||||
|
||||
|
||||
@ -1,403 +0,0 @@
|
||||
# Guide de la Communauté - docv
|
||||
|
||||
## 🌟 Bienvenue dans la Communauté docv !
|
||||
|
||||
Ce guide vous accompagne dans votre participation à la communauté open source de docv, une infrastructure complète pour les paiements silencieux Bitcoin.
|
||||
|
||||
## 🎯 À Propos de docv
|
||||
|
||||
### **Qu'est-ce que docv ?**
|
||||
|
||||
docv est une infrastructure Docker complète qui permet de déployer et gérer facilement un écosystème Bitcoin complet incluant :
|
||||
|
||||
- **Bitcoin Core** : Nœud Bitcoin avec support signet
|
||||
- **Blindbit** : Service de filtres pour les paiements silencieux
|
||||
- **SDK Relay** : Système de relais avec synchronisation mesh
|
||||
- **Tor** : Proxy anonyme pour la confidentialité
|
||||
|
||||
### **Pourquoi les Paiements Silencieux ?**
|
||||
|
||||
Les paiements silencieux (Silent Payments) sont une innovation Bitcoin qui améliore la confidentialité en permettant de créer des adresses uniques pour chaque transaction, sans révéler de liens entre les paiements.
|
||||
|
||||
## 🤝 Comment Contribuer
|
||||
|
||||
### **Niveaux de Contribution**
|
||||
|
||||
#### 🟢 **Débutant**
|
||||
- **Documentation** : Améliorer les guides, corriger les fautes
|
||||
- **Tests** : Ajouter des tests, signaler des bugs
|
||||
- **Support** : Aider les autres utilisateurs
|
||||
- **Traduction** : Traduire la documentation
|
||||
|
||||
#### 🟡 **Intermédiaire**
|
||||
- **Fonctionnalités** : Implémenter de nouvelles fonctionnalités
|
||||
- **Optimisations** : Améliorer les performances
|
||||
- **Tests avancés** : Tests d'intégration et de performance
|
||||
- **Outils** : Créer des scripts et outils
|
||||
|
||||
#### 🔴 **Avancé**
|
||||
- **Architecture** : Améliorer l'architecture du système
|
||||
- **Sécurité** : Audits de sécurité, améliorations
|
||||
- **Core features** : Fonctionnalités principales
|
||||
- **Mentorat** : Guider les nouveaux contributeurs
|
||||
|
||||
### **Premiers Pas**
|
||||
|
||||
#### 1. **Fork et Clone**
|
||||
```bash
|
||||
# Fork le repository sur Gitea
|
||||
# Puis clonez votre fork
|
||||
git clone https://git.4nkweb.com/votre-username/4NK_node.git
|
||||
cd 4NK_node
|
||||
|
||||
# Ajoutez l'upstream
|
||||
git remote add upstream https://git.4nkweb.com/4nk/4NK_node.git
|
||||
```
|
||||
|
||||
#### 2. **Installation Locale**
|
||||
```bash
|
||||
# Installez l'infrastructure
|
||||
./restart_4nk_node.sh
|
||||
|
||||
# Vérifiez que tout fonctionne
|
||||
docker ps
|
||||
```
|
||||
|
||||
#### 3. **Exploration**
|
||||
```bash
|
||||
# Explorez la documentation
|
||||
ls docs/
|
||||
cat docs/INDEX.md
|
||||
|
||||
# Exécutez les tests
|
||||
./tests/run_all_tests.sh
|
||||
```
|
||||
|
||||
## 📚 Ressources d'Apprentissage
|
||||
|
||||
### **Documentation Essentielle**
|
||||
|
||||
#### **Pour Commencer**
|
||||
- **[Guide d'Installation](docs/INSTALLATION.md)** - Installation complète
|
||||
- **[Guide d'Utilisation](docs/USAGE.md)** - Utilisation quotidienne
|
||||
- **[Guide de Configuration](docs/CONFIGURATION.md)** - Configuration avancée
|
||||
|
||||
#### **Pour Développer**
|
||||
- **[Architecture Technique](docs/ARCHITECTURE.md)** - Architecture détaillée
|
||||
- **[API Reference](docs/API.md)** - Documentation des APIs
|
||||
- **[Guide de Tests](docs/TESTING.md)** - Tests et validation
|
||||
|
||||
#### **Pour Contribuer**
|
||||
- **[Guide de Contribution](CONTRIBUTING.md)** - Processus de contribution
|
||||
- **[Code de Conduite](CODE_OF_CONDUCT.md)** - Règles de la communauté
|
||||
- **[Politique de Sécurité](SECURITY.md)** - Signalement de vulnérabilités
|
||||
|
||||
### **Ressources Externes**
|
||||
|
||||
#### **Bitcoin et Paiements Silencieux**
|
||||
- [Bitcoin.org](https://bitcoin.org/) - Documentation Bitcoin officielle
|
||||
- [BIP 352](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki) - Spécification des paiements silencieux
|
||||
- [Bitcoin Core Documentation](https://bitcoincore.org/en/doc/) - Documentation Bitcoin Core
|
||||
|
||||
#### **Technologies Utilisées**
|
||||
- [Docker Documentation](https://docs.docker.com/) - Guide Docker
|
||||
- [Rust Book](https://doc.rust-lang.org/book/) - Guide Rust
|
||||
- [WebSocket RFC](https://tools.ietf.org/html/rfc6455) - Spécification WebSocket
|
||||
|
||||
## 🛠️ Environnement de Développement
|
||||
|
||||
### **Prérequis**
|
||||
|
||||
#### **Système**
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt update
|
||||
sudo apt install docker.io docker-compose git curl
|
||||
|
||||
# CentOS/RHEL
|
||||
sudo yum install docker docker-compose git curl
|
||||
|
||||
# macOS
|
||||
brew install docker docker-compose git curl
|
||||
```
|
||||
|
||||
#### **Développement**
|
||||
```bash
|
||||
# Rust (pour sdk_relay)
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
|
||||
# Python (pour les tests)
|
||||
sudo apt install python3 python3-pip
|
||||
pip3 install websockets requests
|
||||
```
|
||||
|
||||
### **Configuration de Développement**
|
||||
|
||||
#### **Variables d'Environnement**
|
||||
```bash
|
||||
# Configuration de développement
|
||||
export RUST_LOG=debug
|
||||
export ENABLE_SYNC_TEST=1
|
||||
export BITCOIN_NETWORK=signet
|
||||
```
|
||||
|
||||
#### **Outils de Développement**
|
||||
```bash
|
||||
# Linting et formatting
|
||||
cargo clippy
|
||||
cargo fmt
|
||||
|
||||
# Tests
|
||||
cargo test
|
||||
./tests/run_all_tests.sh
|
||||
|
||||
# Build
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## 🐛 Signaler un Bug
|
||||
|
||||
### **Avant de Signaler**
|
||||
|
||||
1. **Vérifiez la documentation** - La solution pourrait déjà être documentée
|
||||
2. **Recherchez les issues existantes** - Le bug pourrait déjà être signalé
|
||||
3. **Testez sur la dernière version** - Le bug pourrait déjà être corrigé
|
||||
|
||||
### **Template de Bug Report**
|
||||
|
||||
Utilisez le template fourni dans Gitea ou suivez cette structure :
|
||||
|
||||
```markdown
|
||||
## Description du Bug
|
||||
Description claire et concise du problème.
|
||||
|
||||
## Étapes pour Reproduire
|
||||
1. Aller à '...'
|
||||
2. Cliquer sur '...'
|
||||
3. Faire défiler jusqu'à '...'
|
||||
4. Voir l'erreur
|
||||
|
||||
## Comportement Attendu
|
||||
Description de ce qui devrait se passer.
|
||||
|
||||
## Comportement Actuel
|
||||
Description de ce qui se passe actuellement.
|
||||
|
||||
## Informations Système
|
||||
- OS: [ex: Ubuntu 20.04]
|
||||
- Docker: [ex: 20.10.0]
|
||||
- Version: [ex: v1.0.0]
|
||||
|
||||
## Logs
|
||||
```
|
||||
Logs pertinents ici
|
||||
```
|
||||
```
|
||||
|
||||
## 💡 Proposer une Fonctionnalité
|
||||
|
||||
### **Avant de Proposer**
|
||||
|
||||
1. **Vérifiez la roadmap** - La fonctionnalité pourrait déjà être planifiée
|
||||
2. **Discutez avec la communauté** - Utilisez les discussions Gitea
|
||||
3. **Préparez un prototype** - Montrez que c'est faisable
|
||||
|
||||
### **Template de Feature Request**
|
||||
|
||||
```markdown
|
||||
## Résumé
|
||||
Description claire et concise de la fonctionnalité souhaitée.
|
||||
|
||||
## Motivation
|
||||
Pourquoi cette fonctionnalité est-elle nécessaire ?
|
||||
|
||||
## Proposition
|
||||
Description détaillée de la fonctionnalité proposée.
|
||||
|
||||
## Alternatives Considérées
|
||||
Autres solutions envisagées.
|
||||
|
||||
## Exemples d'Utilisation
|
||||
Comment cette fonctionnalité serait-elle utilisée ?
|
||||
```
|
||||
|
||||
## 🔄 Processus de Contribution
|
||||
|
||||
### **Workflow Git**
|
||||
|
||||
#### 1. **Créer une Branche**
|
||||
```bash
|
||||
# Depuis la branche main
|
||||
git checkout main
|
||||
git pull upstream main
|
||||
|
||||
# Créer une branche pour votre contribution
|
||||
git checkout -b feature/nom-de-votre-feature
|
||||
# ou
|
||||
git checkout -b fix/nom-du-bug
|
||||
```
|
||||
|
||||
#### 2. **Développer**
|
||||
```bash
|
||||
# Développez votre fonctionnalité
|
||||
# Ajoutez des tests
|
||||
# Mettez à jour la documentation
|
||||
|
||||
# Commitez régulièrement
|
||||
git add .
|
||||
git commit -m "feat: ajouter nouvelle fonctionnalité"
|
||||
```
|
||||
|
||||
#### 3. **Tester**
|
||||
```bash
|
||||
# Exécutez les tests
|
||||
./tests/run_all_tests.sh
|
||||
|
||||
# Vérifiez le code
|
||||
cargo clippy
|
||||
cargo fmt --check
|
||||
```
|
||||
|
||||
#### 4. **Soumettre**
|
||||
```bash
|
||||
# Poussez vers votre fork
|
||||
git push origin feature/nom-de-votre-feature
|
||||
|
||||
# Créez une Pull Request sur Gitea
|
||||
```
|
||||
|
||||
### **Standards de Code**
|
||||
|
||||
#### **Messages de Commit**
|
||||
Utilisez le format conventionnel :
|
||||
```bash
|
||||
feat(sdk_relay): add new sync type for metrics
|
||||
fix(bitcoin): resolve connection timeout issue
|
||||
docs(api): update WebSocket message format
|
||||
test(integration): add multi-relay sync tests
|
||||
```
|
||||
|
||||
#### **Code Style**
|
||||
- **Rust** : Suivez les conventions Rust (rustfmt, clippy)
|
||||
- **Bash** : Utilisez shellcheck pour les scripts
|
||||
- **Python** : Suivez PEP 8
|
||||
- **Markdown** : Utilisez un linter markdown
|
||||
|
||||
## 🏷️ Labels et Milestones
|
||||
|
||||
### **Labels Utilisés**
|
||||
|
||||
#### **Type**
|
||||
- `bug` - Problèmes et bugs
|
||||
- `enhancement` - Nouvelles fonctionnalités
|
||||
- `documentation` - Amélioration de la documentation
|
||||
- `good first issue` - Pour les nouveaux contributeurs
|
||||
- `help wanted` - Besoin d'aide
|
||||
|
||||
#### **Priorité**
|
||||
- `priority: high` - Priorité élevée
|
||||
- `priority: medium` - Priorité moyenne
|
||||
- `priority: low` - Priorité basse
|
||||
|
||||
#### **Statut**
|
||||
- `status: blocked` - Bloqué
|
||||
- `status: in progress` - En cours
|
||||
- `status: ready for review` - Prêt pour review
|
||||
|
||||
### **Milestones**
|
||||
|
||||
- **v1.0.0** - Version stable initiale
|
||||
- **v1.1.0** - Améliorations et corrections
|
||||
- **v2.0.0** - Nouvelles fonctionnalités majeures
|
||||
|
||||
## 🎉 Reconnaissance
|
||||
|
||||
### **Hall of Fame**
|
||||
|
||||
Les contributeurs significatifs seront reconnus dans :
|
||||
|
||||
- **README.md** - Liste des contributeurs
|
||||
- **CHANGELOG.md** - Mentions dans les releases
|
||||
- **Documentation** - Crédits dans les guides
|
||||
- **Site web** - Page dédiée aux contributeurs
|
||||
|
||||
### **Badges et Certifications**
|
||||
|
||||
- **Contributeur Bronze** : 1-5 contributions
|
||||
- **Contributeur Argent** : 6-20 contributions
|
||||
- **Contributeur Or** : 21+ contributions
|
||||
- **Maintainer** : Responsabilités de maintenance
|
||||
|
||||
## 🆘 Besoin d'Aide ?
|
||||
|
||||
### **Canaux de Support**
|
||||
|
||||
#### **Issues Gitea**
|
||||
- **Bugs** : [Issues](https://git.4nkweb.com/4nk/4NK_node/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
- **Fonctionnalités** : [Feature Requests](https://git.4nkweb.com/4nk/4NK_node/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
|
||||
|
||||
#### **Discussions**
|
||||
- **Questions générales** : [Discussions](https://git.4nkweb.com/4nk/4NK_node/issues)
|
||||
- **Aide technique** : [Support](https://git.4nkweb.com/4nk/4NK_node/issues/new)
|
||||
|
||||
#### **Contact Direct**
|
||||
- **Email** : support@4nkweb.com
|
||||
- **Sécurité** : security@4nkweb.com
|
||||
|
||||
### **FAQ**
|
||||
|
||||
#### **Questions Fréquentes**
|
||||
|
||||
**Q: Comment installer docv ?**
|
||||
A: Suivez le [Guide d'Installation](docs/INSTALLATION.md)
|
||||
|
||||
**Q: Comment contribuer au code ?**
|
||||
A: Consultez le [Guide de Contribution](CONTRIBUTING.md)
|
||||
|
||||
**Q: Comment signaler un bug de sécurité ?**
|
||||
A: Contactez security@4nkweb.com (NE PAS créer d'issue publique)
|
||||
|
||||
**Q: Comment proposer une nouvelle fonctionnalité ?**
|
||||
A: Créez une issue avec le label `enhancement`
|
||||
|
||||
## 🚀 Projets Futurs
|
||||
|
||||
### **Roadmap Communautaire**
|
||||
|
||||
#### **Court Terme (1-3 mois)**
|
||||
- Interface utilisateur web
|
||||
- Support de nouveaux réseaux Bitcoin
|
||||
- Amélioration de la documentation
|
||||
- Tests de performance
|
||||
|
||||
#### **Moyen Terme (3-6 mois)**
|
||||
- Support Lightning Network
|
||||
- API REST complète
|
||||
- Monitoring avancé
|
||||
- Déploiement cloud
|
||||
|
||||
#### **Long Terme (6-12 mois)**
|
||||
- Écosystème complet
|
||||
- Marketplace d'extensions
|
||||
- Support multi-blockchains
|
||||
- IA et automatisation
|
||||
|
||||
### **Idées de Contribution**
|
||||
|
||||
#### **Fonctionnalités Populaires**
|
||||
- Interface graphique pour la gestion
|
||||
- Intégration avec des wallets populaires
|
||||
- Support de nouveaux types de paiements
|
||||
- Outils de monitoring avancés
|
||||
|
||||
#### **Améliorations Techniques**
|
||||
- Optimisation des performances
|
||||
- Amélioration de la sécurité
|
||||
- Support de nouvelles plateformes
|
||||
- Tests automatisés avancés
|
||||
|
||||
---
|
||||
|
||||
**Merci de faire partie de la communauté docv ! Votre contribution aide à construire l'avenir des paiements Bitcoin privés et sécurisés.** 🌟
|
||||
|
||||
|
||||
@ -1,214 +0,0 @@
|
||||
# ⚙️ Guide de Configuration - docv
|
||||
|
||||
Guide complet pour configurer l'infrastructure docv selon vos besoins.
|
||||
|
||||
## 📋 Configuration Générale
|
||||
|
||||
### 1. Variables d'Environnement
|
||||
|
||||
Créer un fichier `.env` à la racine du projet :
|
||||
|
||||
|
||||
### 2. Configuration Réseau
|
||||
|
||||
#### Réseau Docker Personnalisé
|
||||
|
||||
|
||||
#### Configuration de Pare-feu
|
||||
|
||||
## 🔧 Configuration Bitcoin Core
|
||||
|
||||
### 1. Configuration de Base
|
||||
|
||||
|
||||
### 2. Configuration Avancée
|
||||
|
||||
#### Sécurité
|
||||
|
||||
|
||||
## 🔧 Configuration SSL/TLS
|
||||
|
||||
### 1. Certificat Auto-Signé
|
||||
|
||||
```bash
|
||||
# Générer un certificat auto-signé
|
||||
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
|
||||
|
||||
# Configurer nginx comme proxy SSL
|
||||
cat > nginx.conf << EOF
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name your-domain.com;
|
||||
|
||||
ssl_certificate cert.pem;
|
||||
ssl_certificate_key key.pem;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8090;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### 2. Certificat Let's Encrypt
|
||||
|
||||
```bash
|
||||
# Installer certbot
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
|
||||
# Obtenir un certificat
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
|
||||
# Configuration automatique
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
## 🔧 Configuration de Monitoring
|
||||
|
||||
### 1. Prometheus
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml addition
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: prometheus
|
||||
ports:
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus_data:/prometheus
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--web.console.libraries=/etc/prometheus/console_libraries'
|
||||
- '--web.console.templates=/etc/prometheus/consoles'
|
||||
- '--storage.tsdb.retention.time=200h'
|
||||
- '--web.enable-lifecycle'
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: grafana
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
|
||||
volumes:
|
||||
prometheus_data:
|
||||
grafana_data:
|
||||
```
|
||||
|
||||
### 2. Configuration Prometheus
|
||||
|
||||
Fichier : `prometheus.yml`
|
||||
|
||||
```yaml
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
rule_files:
|
||||
# - "first_rules.yml"
|
||||
# - "second_rules.yml"
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'bitcoin'
|
||||
static_configs:
|
||||
- targets: ['bitcoin:18443']
|
||||
|
||||
- job_name: 'blindbit'
|
||||
static_configs:
|
||||
- targets: ['blindbit:8000']
|
||||
|
||||
- job_name: 'sdk_relay'
|
||||
static_configs:
|
||||
- targets: ['sdk_relay_1:8091']
|
||||
```
|
||||
|
||||
## 🔧 Configuration de Sauvegarde
|
||||
|
||||
### 1. Script de Sauvegarde
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup_4nk.sh
|
||||
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_DIR="/backup/4nk_node_$DATE"
|
||||
|
||||
mkdir -p $BACKUP_DIR
|
||||
```
|
||||
|
||||
### 2. Configuration Cron
|
||||
|
||||
```bash
|
||||
# Ajouter au cron pour sauvegarde automatique
|
||||
```
|
||||
|
||||
## 🔧 Configuration de Logs
|
||||
|
||||
### 1. Rotation des Logs
|
||||
|
||||
```bash
|
||||
# Configuration logrotate
|
||||
```
|
||||
|
||||
### 2. Centralisation des Logs
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml addition
|
||||
services:
|
||||
elasticsearch:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
|
||||
container_name: elasticsearch
|
||||
environment:
|
||||
- discovery.type=single-node
|
||||
ports:
|
||||
- "9200:9200"
|
||||
volumes:
|
||||
- elasticsearch_data:/usr/share/elasticsearch/data
|
||||
|
||||
kibana:
|
||||
image: docker.elastic.co/kibana/kibana:7.17.0
|
||||
container_name: kibana
|
||||
ports:
|
||||
- "5601:5601"
|
||||
depends_on:
|
||||
- elasticsearch
|
||||
|
||||
filebeat:
|
||||
image: docker.elastic.co/beats/filebeat:7.17.0
|
||||
container_name: filebeat
|
||||
volumes:
|
||||
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||
- ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
|
||||
depends_on:
|
||||
- elasticsearch
|
||||
|
||||
volumes:
|
||||
elasticsearch_data:
|
||||
```
|
||||
|
||||
## 📝 Checklist de Configuration
|
||||
|
||||
|
||||
## 🎯 Commandes de Configuration
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,288 +0,0 @@
|
||||
# Configuration Gitea - docv
|
||||
|
||||
Ce guide explique comment configurer votre projet (docv) sur une forge Gitea (adaptable à GitHub/GitLab).
|
||||
|
||||
## 🎯 Configuration Gitea
|
||||
|
||||
### Repository Configuration
|
||||
|
||||
Le projet est hébergé sur : **https://github.com/ncantuNewAccount/docv**
|
||||
|
||||
Note: ce dépôt est un modèle. Les projets dérivés doivent conserver la CI et les règles Cursor (dont le job `release-guard`).
|
||||
|
||||
### Branches Principales
|
||||
|
||||
- **`main`** - Branche principale, code stable
|
||||
- **`develop`** - Branche de développement (optionnelle)
|
||||
- **`feature/*`** - Branches de fonctionnalités
|
||||
- **`fix/*`** - Branches de corrections
|
||||
|
||||
### Protection des Branches
|
||||
|
||||
Configurez les protections suivantes sur Gitea :
|
||||
|
||||
1. **Branche `main`** :
|
||||
- ✅ Require pull request reviews before merging
|
||||
- ✅ Require status checks to pass before merging
|
||||
- ✅ Require branches to be up to date before merging
|
||||
- ✅ Restrict pushes that create files
|
||||
- ✅ Restrict pushes that delete files
|
||||
|
||||
2. **Branche `develop`** (si utilisée) :
|
||||
- ✅ Require pull request reviews before merging
|
||||
- ✅ Require status checks to pass before merging
|
||||
|
||||
## 🔧 Configuration CI/CD
|
||||
|
||||
### Option 1 : Gitea Actions (Recommandé)
|
||||
|
||||
Si votre instance Gitea supporte Gitea Actions :
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/ci.yml
|
||||
name: CI - docv
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd sdk_relay
|
||||
cargo test
|
||||
```
|
||||
|
||||
### Option 2 : Runner Externe
|
||||
|
||||
Configurez un runner CI/CD externe (Jenkins, GitLab CI, etc.) :
|
||||
|
||||
```bash
|
||||
# Exemple avec Jenkins
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'cd sdk_relay && cargo test'
|
||||
}
|
||||
}
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'docker-compose build'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option 3 : GitHub Actions (Migration)
|
||||
|
||||
Si vous souhaitez utiliser GitHub Actions avec un miroir :
|
||||
|
||||
1. Créez un repository miroir sur GitHub
|
||||
2. Configurez un webhook pour synchroniser automatiquement
|
||||
3. Utilisez le workflow GitHub Actions existant
|
||||
|
||||
## 📋 Templates Gitea
|
||||
|
||||
### Issues Templates
|
||||
|
||||
Les templates d'issues sont stockés dans `.gitea/ISSUE_TEMPLATE/` :
|
||||
|
||||
- `bug_report.md` - Pour signaler des bugs
|
||||
- `feature_request.md` - Pour proposer des fonctionnalités
|
||||
|
||||
### Pull Request Template
|
||||
|
||||
Le template de PR est dans `.gitea/PULL_REQUEST_TEMPLATE.md`
|
||||
|
||||
## 🔗 Intégrations Gitea
|
||||
|
||||
### Webhooks
|
||||
|
||||
Configurez des webhooks pour :
|
||||
|
||||
1. **Notifications** - Slack, Discord, Email
|
||||
2. **CI/CD** - Déclenchement automatique des builds
|
||||
3. **Deployment** - Déploiement automatique
|
||||
|
||||
### Release Guard (recommandé)
|
||||
- Activer/conserver le job `release-guard` dans `.gitea/workflows/ci.yml`
|
||||
- Objectifs: tests verts, documentation à jour, build OK, alignement `TEMPLATE_VERSION` ↔ `CHANGELOG.md` ↔ tag
|
||||
- En local: `RELEASE_TYPE=ci-verify scripts/release/guard.sh`
|
||||
|
||||
### API Gitea
|
||||
|
||||
Utilisez l'API Gitea pour l'automatisation :
|
||||
|
||||
```bash
|
||||
# Exemple : Créer une release
|
||||
curl -X POST "https://github.com/api/v1/repos/ncantuNewAccount/docv/releases" \
|
||||
-H "Authorization: token YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tag_name": "v1.0.0",
|
||||
"name": "Release v1.0.0",
|
||||
"body": "Description de la release"
|
||||
}'
|
||||
```
|
||||
|
||||
## 🏷️ Labels et Milestones
|
||||
|
||||
### Labels Recommandés
|
||||
|
||||
- **bug** - Problèmes et bugs
|
||||
- **enhancement** - Nouvelles fonctionnalités
|
||||
- **documentation** - Amélioration de la documentation
|
||||
- **good first issue** - Pour les nouveaux contributeurs
|
||||
- **help wanted** - Besoin d'aide
|
||||
- **priority: high** - Priorité élevée
|
||||
- **priority: low** - Priorité basse
|
||||
- **status: blocked** - Bloqué
|
||||
- **status: in progress** - En cours
|
||||
- **status: ready for review** - Prêt pour review
|
||||
|
||||
### Milestones
|
||||
|
||||
- **v1.0.0** - Version stable initiale
|
||||
- **v1.1.0** - Améliorations et corrections
|
||||
- **v2.0.0** - Nouvelles fonctionnalités majeures
|
||||
|
||||
## 🔐 Sécurité Gitea
|
||||
|
||||
### Permissions
|
||||
|
||||
1. **Repository** :
|
||||
- Public pour l'open source
|
||||
- Issues et PR activés
|
||||
- Wiki activé (optionnel)
|
||||
|
||||
2. **Collaborateurs** :
|
||||
- Maintainers : Write access
|
||||
- Contributors : Read access
|
||||
- Public : Read access
|
||||
|
||||
### Secrets
|
||||
|
||||
Stockez les secrets sensibles dans les variables d'environnement Gitea :
|
||||
|
||||
- `DOCKER_USERNAME`
|
||||
- `DOCKER_PASSWORD`
|
||||
- `GITEA_TOKEN`
|
||||
- `SLACK_WEBHOOK_URL`
|
||||
|
||||
## 📊 Monitoring et Analytics
|
||||
|
||||
### Gitea Analytics
|
||||
|
||||
- **Traffic** - Vues du repository
|
||||
- **Contributors** - Contributeurs actifs
|
||||
- **Issues** - Statistiques des issues
|
||||
- **Pull Requests** - Statistiques des PR
|
||||
|
||||
### Intégrations Externes
|
||||
|
||||
- **Codecov** - Couverture de code
|
||||
- **SonarCloud** - Qualité du code
|
||||
- **Dependabot** - Mise à jour des dépendances
|
||||
|
||||
## 🚀 Workflow de Contribution
|
||||
|
||||
### 1. Fork et Clone
|
||||
|
||||
```bash
|
||||
# Fork sur Gitea
|
||||
# Puis clone
|
||||
git clone https://github.com/<USERNAME>/docv.git
|
||||
cd docv
|
||||
|
||||
# Ajouter l'upstream
|
||||
git remote add upstream https://github.com/ncantuNewAccount/docv.git
|
||||
```
|
||||
|
||||
### 2. Développement
|
||||
|
||||
```bash
|
||||
# Créer une branche
|
||||
git checkout -b feature/nouvelle-fonctionnalite
|
||||
|
||||
# Développer
|
||||
# ...
|
||||
|
||||
# Commiter
|
||||
git commit -m "feat: ajouter nouvelle fonctionnalité"
|
||||
|
||||
# Pousser
|
||||
git push origin feature/nouvelle-fonctionnalite
|
||||
```
|
||||
|
||||
### 3. Pull Request
|
||||
|
||||
1. Créer une PR sur Gitea
|
||||
2. Remplir le template
|
||||
3. Attendre les reviews
|
||||
4. Merge après approbation
|
||||
|
||||
## 🔧 Configuration Avancée
|
||||
|
||||
### Gitea Configuration
|
||||
|
||||
```ini
|
||||
# gitea.ini
|
||||
[repository]
|
||||
DEFAULT_BRANCH = main
|
||||
PUSH_CREATE_DELETE_PROTECTED_BRANCH = true
|
||||
|
||||
[repository.pull-request]
|
||||
ENABLE_WHITELIST = true
|
||||
WHITELIST_USERS = admin,maintainer
|
||||
```
|
||||
|
||||
### Webhooks Configuration
|
||||
|
||||
```yaml
|
||||
# webhook.yml
|
||||
url: "https://your-ci-server.com/webhook"
|
||||
content_type: "application/json"
|
||||
secret: "your-secret"
|
||||
events:
|
||||
- push
|
||||
- pull_request
|
||||
- issues
|
||||
```
|
||||
|
||||
## 📚 Ressources
|
||||
|
||||
### Documentation Gitea
|
||||
|
||||
- [Gitea Documentation](https://docs.gitea.io/)
|
||||
- [Gitea API](https://docs.gitea.io/en-us/api-usage/)
|
||||
- [Gitea Actions](https://docs.gitea.io/en-us/actions/)
|
||||
|
||||
### Outils Utiles
|
||||
|
||||
- **Gitea CLI** - Interface en ligne de commande
|
||||
- **Gitea SDK** - SDK pour l'automatisation
|
||||
- **Gitea Runner** - Runner pour les actions
|
||||
|
||||
---
|
||||
|
||||
**Configuration Gitea terminée ! Le projet est prêt pour l'open source sur votre forge** 🚀
|
||||
|
||||
|
||||
306
docs/INDEX.md
306
docs/INDEX.md
@ -1,306 +0,0 @@
|
||||
# 📚 Index de Documentation - docv
|
||||
|
||||
Index complet de la documentation de l'infrastructure docv.
|
||||
|
||||
## 📖 Guides Principaux
|
||||
|
||||
### 🚀 [Guide d'Installation](INSTALLATION.md)
|
||||
|
||||
|
||||
### 📖 [Guide d'Utilisation](USAGE.md)
|
||||
|
||||
|
||||
### ⚙️ [Guide de Configuration](CONFIGURATION.md)
|
||||
|
||||
## 🔧 Guides Techniques
|
||||
|
||||
### 🏗️ [Architecture Technique](ARCHITECTURE.md)
|
||||
|
||||
|
||||
### 📡 [API Reference](API.md)
|
||||
|
||||
|
||||
### 🔒 [Sécurité](SECURITY.md)
|
||||
Guide de sécurité et bonnes pratiques.
|
||||
- **Authentification et autorisation**
|
||||
- **Chiffrement et certificats**
|
||||
- **Isolation réseau**
|
||||
- **Audit et monitoring de sécurité**
|
||||
- **Bonnes pratiques**
|
||||
|
||||
### 🐙 [Configuration Gitea](GITEA_SETUP.md)
|
||||
Guide de configuration spécifique pour Gitea.
|
||||
- **Configuration du repository Gitea**
|
||||
- **Templates d'issues et pull requests**
|
||||
- **Configuration CI/CD avec Gitea Actions**
|
||||
- **Intégrations et webhooks**
|
||||
- **Workflow de contribution**
|
||||
- **Sécurité et permissions**
|
||||
|
||||
### 🚀 [Plan de Release](RELEASE_PLAN.md)
|
||||
Plan de lancement open source complet.
|
||||
- **Phases de préparation**
|
||||
- **Communication et marketing**
|
||||
- **Checklist de lancement**
|
||||
- **Support communautaire**
|
||||
- **Gestion des risques**
|
||||
- **Release Guard** (obligatoire): tests/doc/build/alignement version/changelog/tag, choix latest vs wip
|
||||
|
||||
### 🌟 [Guide de la Communauté](COMMUNITY_GUIDE.md)
|
||||
Guide complet pour la communauté.
|
||||
- **Comment contribuer**
|
||||
- **Ressources d'apprentissage**
|
||||
- **Environnement de développement**
|
||||
- **Processus de contribution**
|
||||
- **Support et reconnaissance**
|
||||
|
||||
### 🗺️ [Roadmap](ROADMAP.md)
|
||||
Roadmap de développement détaillée.
|
||||
- **Timeline de développement**
|
||||
- **Fonctionnalités planifiées**
|
||||
- **Évolution de l'architecture**
|
||||
- **Métriques de succès**
|
||||
- **Vision long terme**
|
||||
|
||||
### 📈 [Performance](PERFORMANCE.md)
|
||||
Guide d'optimisation et monitoring des performances.
|
||||
- **Optimisation des ressources**
|
||||
- **Monitoring des performances**
|
||||
- **Tests de charge**
|
||||
- **Métriques et alertes**
|
||||
- **Troubleshooting des performances**
|
||||
|
||||
## 🧪 Guides de Test
|
||||
|
||||
### 🧪 [Guide de Tests](TESTING.md)
|
||||
Guide complet des tests de l'infrastructure docv.
|
||||
- **Tests unitaires** : Tests individuels des composants
|
||||
- **Tests d'intégration** : Tests d'interaction entre services
|
||||
- **Tests de connectivité** : Tests réseau et WebSocket
|
||||
- **Tests externes** : Tests avec des nœuds externes
|
||||
- **Tests de performance** : Tests de charge et performance (à venir)
|
||||
- **Organisation et exécution des tests**
|
||||
- **Interprétation des résultats**
|
||||
- **Dépannage et maintenance**
|
||||
|
||||
### 🔄 [Tests de Synchronisation](SYNC_TESTING.md)
|
||||
Guide des tests de synchronisation entre relais.
|
||||
- **Tests de synchronisation mesh**
|
||||
- **Tests de découverte de relais**
|
||||
- **Tests de cache de déduplication**
|
||||
- **Tests de métriques de synchronisation**
|
||||
- **Troubleshooting de la synchronisation**
|
||||
|
||||
### 📊 [Tests de Performance](PERFORMANCE_TESTING.md)
|
||||
Guide des tests de performance et de charge.
|
||||
- **Tests de charge WebSocket**
|
||||
- **Tests de performance Bitcoin Core**
|
||||
- **Tests de performance Blindbit**
|
||||
- **Tests de scalabilité**
|
||||
- **Benchmarks et métriques**
|
||||
|
||||
## 🌐 Guides Réseau
|
||||
|
||||
### 🌐 [Réseau de Relais](RELAY_NETWORK.md)
|
||||
Guide de configuration du réseau mesh de relais.
|
||||
- **Architecture mesh**
|
||||
- **Configuration des relais locaux**
|
||||
- **Synchronisation entre relais**
|
||||
- **Découverte automatique**
|
||||
- **Gestion des connexions**
|
||||
|
||||
### 🌍 [Nœuds Externes](EXTERNAL_NODES.md)
|
||||
Guide d'ajout et de gestion de nœuds externes.
|
||||
- **Configuration des nœuds externes**
|
||||
- **Script d'administration**
|
||||
- **Validation et sécurité**
|
||||
- **Tests de connectivité**
|
||||
- **Gestion multi-sites**
|
||||
|
||||
### 🔄 [Synchronisation](SYNCHRONIZATION.md)
|
||||
Guide du protocole de synchronisation.
|
||||
- **Protocole de synchronisation**
|
||||
- **Types de messages**
|
||||
- **Cache de déduplication**
|
||||
- **Métriques de synchronisation**
|
||||
- **Troubleshooting**
|
||||
|
||||
## 📋 Guides de Référence
|
||||
|
||||
### 📋 [Commandes Rapides](QUICK_REFERENCE.md)
|
||||
Référence rapide des commandes essentielles.
|
||||
- **Commandes de démarrage**
|
||||
- **Commandes de monitoring**
|
||||
- **Commandes de test**
|
||||
- **Commandes de dépannage**
|
||||
- **Commandes de maintenance**
|
||||
|
||||
### 📋 [Troubleshooting](TROUBLESHOOTING.md)
|
||||
Guide de résolution des problèmes courants.
|
||||
- **Problèmes de démarrage**
|
||||
- **Problèmes de connectivité**
|
||||
- **Problèmes de synchronisation**
|
||||
- **Problèmes de performance**
|
||||
- **Logs et diagnostics**
|
||||
|
||||
### 📋 [FAQ](FAQ.md)
|
||||
Questions fréquemment posées.
|
||||
- **Questions d'installation**
|
||||
- **Questions de configuration**
|
||||
- **Questions d'utilisation**
|
||||
- **Questions de performance**
|
||||
- **Questions de sécurité**
|
||||
|
||||
## 📁 Structure des Fichiers
|
||||
|
||||
```
|
||||
4NK_node/
|
||||
|
||||
├── .cursor
|
||||
│ ├── .cursorignore
|
||||
│ ├── rules
|
||||
│ │ ├── 00-foundations.mdc
|
||||
│ │ ├── 10-project-structure.mdc
|
||||
│ │ ├── 20-documentation.mdc
|
||||
│ │ ├── 30-testing.mdc
|
||||
│ │ ├── 40-dependencies-and-build.mdc
|
||||
│ │ ├── 41-ssh-automation.mdc
|
||||
│ │ ├── 50-data-csv-models.mdc
|
||||
│ │ ├── 60-office-docs.mdc
|
||||
│ │ ├── 70-frontend-architecture.mdc
|
||||
│ │ ├── 80-versioning-and-release.mdc
|
||||
│ │ ├── 90-gitea-and-oss.mdc
|
||||
│ │ └── 95-triage-and-problem-solving.mdc
|
||||
│ └── ruleset-index.md
|
||||
└── .gitea
|
||||
├── ISSUE_TEMPLATE
|
||||
│ ├── bug_report.md
|
||||
│ └── feature_request.md
|
||||
├── PULL_REQUEST_TEMPLATE.md
|
||||
└── workflows
|
||||
└── ci.yml
|
||||
├── AGENTS.md
|
||||
├── archive
|
||||
├── CHANGELOG.md
|
||||
├── CODE_OF_CONDUCT.md
|
||||
├── CONTRIBUTING.md
|
||||
├── docs
|
||||
│ ├── API.md
|
||||
│ ├── ARCHITECTURE.md
|
||||
│ ├── AUTO_SSH_PUSH.md
|
||||
│ ├── COMMUNITY_GUIDE.md
|
||||
│ ├── CONFIGURATION.md
|
||||
│ ├── GITEA_SETUP.md
|
||||
│ ├── INDEX.md
|
||||
│ ├── INSTALLATION.md
|
||||
│ ├── MIGRATION.md
|
||||
│ ├── OPEN_SOURCE_CHECKLIST.md
|
||||
│ ├── QUICK_REFERENCE.md
|
||||
│ ├── RELEASE_PLAN.md
|
||||
│ ├── ROADMAP.md
|
||||
│ ├── SECURITY_AUDIT.md
|
||||
│ ├── SSH_SETUP.md
|
||||
│ ├── SSH_USATE.md
|
||||
│ ├── TESTING.md
|
||||
│ └── USAGE.md
|
||||
├── LICENSE
|
||||
├── README.md
|
||||
├── scripts
|
||||
│ ├── auto-ssh-push.sh
|
||||
│ ├── init-ssh-env.sh
|
||||
│ └── setup-ssh-ci.sh
|
||||
├── SECURITY.md
|
||||
```
|
||||
|
||||
## 🎯 Parcours d'Apprentissage
|
||||
|
||||
### 🚀 **Débutant**
|
||||
1. [Guide d'Installation](INSTALLATION.md) - Installer l'infrastructure
|
||||
2. [Guide d'Utilisation](USAGE.md) - Utiliser les services de base
|
||||
3. [Tests de Base](TESTING.md) - Vérifier le fonctionnement
|
||||
4. [FAQ](FAQ.md) - Réponses aux questions courantes
|
||||
|
||||
### 🔧 **Intermédiaire**
|
||||
1. [Guide de Configuration](CONFIGURATION.md) - Configurer selon vos besoins
|
||||
2. [Réseau de Relais](RELAY_NETWORK.md) - Comprendre l'architecture mesh
|
||||
3. [Nœuds Externes](EXTERNAL_NODES.md) - Ajouter des nœuds externes
|
||||
4. [Tests de Synchronisation](SYNC_TESTING.md) - Tester la synchronisation
|
||||
|
||||
### 🏗️ **Avancé**
|
||||
1. [Architecture Technique](ARCHITECTURE.md) - Comprendre l'architecture
|
||||
2. [API Reference](API.md) - Utiliser les APIs
|
||||
3. [Sécurité](SECURITY.md) - Sécuriser l'infrastructure
|
||||
4. [Performance](PERFORMANCE.md) - Optimiser les performances
|
||||
5. [Tests de Performance](PERFORMANCE_TESTING.md) - Tests avancés
|
||||
|
||||
### 🛠️ **Expert**
|
||||
1. [Synchronisation](SYNCHRONIZATION.md) - Protocole de synchronisation
|
||||
2. [Troubleshooting](TROUBLESHOOTING.md) - Résolution de problèmes
|
||||
3. [Commandes Rapides](QUICK_REFERENCE.md) - Référence rapide
|
||||
4. Spécifications techniques dans `/specs/`
|
||||
|
||||
## 🔍 Recherche dans la Documentation
|
||||
|
||||
### Par Sujet
|
||||
- **Installation** : [INSTALLATION.md](INSTALLATION.md)
|
||||
- **Configuration** : [CONFIGURATION.md](CONFIGURATION.md)
|
||||
- **Utilisation** : [USAGE.md](USAGE.md)
|
||||
- **Tests** : [TESTING.md](TESTING.md), [SYNC_TESTING.md](SYNC_TESTING.md)
|
||||
- **Réseau** : [RELAY_NETWORK.md](RELAY_NETWORK.md), [EXTERNAL_NODES.md](EXTERNAL_NODES.md)
|
||||
- **Performance** : [PERFORMANCE.md](PERFORMANCE.md)
|
||||
- **Sécurité** : [SECURITY.md](SECURITY.md)
|
||||
- **Dépannage** : [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
|
||||
|
||||
### Par Service
|
||||
- **Bitcoin Core** : [CONFIGURATION.md](CONFIGURATION.md#configuration-bitcoin-core)
|
||||
- **Blindbit** : [CONFIGURATION.md](CONFIGURATION.md#configuration-blindbit)
|
||||
- **sdk_relay** : [CONFIGURATION.md](CONFIGURATION.md#configuration-des-relais)
|
||||
- **Tor** : [CONFIGURATION.md](CONFIGURATION.md#configuration-tor)
|
||||
|
||||
### Par Tâche
|
||||
- **Démarrer** : [USAGE.md](USAGE.md#démarrage-quotidien)
|
||||
- **Configurer** : [CONFIGURATION.md](CONFIGURATION.md)
|
||||
- **Tester** : [TESTING.md](TESTING.md)
|
||||
- **Monitorer** : [USAGE.md](USAGE.md#monitoring-et-alertes)
|
||||
- **Dépanner** : [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
|
||||
|
||||
## 📞 Support
|
||||
|
||||
### Documentation
|
||||
- **Index** : [INDEX.md](INDEX.md) - Cet index
|
||||
- **FAQ** : [FAQ.md](FAQ.md) - Questions fréquentes
|
||||
- **Troubleshooting** : [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Résolution de problèmes
|
||||
|
||||
### Ressources Externes
|
||||
- **Repository** : https://github.com/ncantuNewAccount/docv
|
||||
- **Issues** : https://github.com/ncantuNewAccount/docv/issues
|
||||
- **Wiki** : https://github.com/ncantuNewAccount/docv/wiki
|
||||
|
||||
### Contact
|
||||
- **Email** : <SUPPORT_EMAIL>
|
||||
- **Chat** : <CHAT_URL>
|
||||
- **Forum** : <FORUM_URL>
|
||||
|
||||
## 🔄 Mise à Jour de la Documentation
|
||||
|
||||
### Dernière Mise à Jour
|
||||
- **Date** : générée à la release
|
||||
- **Version** : pilotée par `TEMPLATE_VERSION` et `CHANGELOG.md`
|
||||
- **Auteur** : ncantuNewAccount
|
||||
|
||||
### Historique des Versions
|
||||
- **v1.0.0** : Documentation initiale complète
|
||||
- **v0.9.0** : Documentation de base
|
||||
- **v0.8.0** : Guides techniques
|
||||
- **v0.7.0** : Guides de test
|
||||
|
||||
### Contribution
|
||||
Pour contribuer à la documentation :
|
||||
1. Fork le repository
|
||||
2. Créer une branche pour votre contribution
|
||||
3. Modifier la documentation
|
||||
4. Créer une Pull Request
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,535 +0,0 @@
|
||||
# 📦 Guide d'Installation - docv
|
||||
|
||||
> Ce document est un modèle générique. Remplacez docv, ncantuNewAccount, <REPO_SSH_URL>, <REPO_HTTPS_URL> et adaptez les sections à votre contexte (Gitea/GitHub/GitLab).
|
||||
|
||||
Guide complet pour installer et configurer l'infrastructure docv.
|
||||
|
||||
## 📋 Prérequis
|
||||
|
||||
### Système
|
||||
|
||||
- **OS** : Linux (Ubuntu 20.04+, Debian 11+, CentOS 8+)
|
||||
- **Architecture** : x86_64
|
||||
- **RAM** : 4 Go minimum, 8 Go recommandés
|
||||
- **Stockage** : 20 Go minimum, 50 Go recommandés
|
||||
- **Réseau** : Connexion Internet stable
|
||||
|
||||
### Logiciels
|
||||
|
||||
- **Docker** : Version 20.10+
|
||||
- **Docker Compose** : Version 2.0+
|
||||
- **Git** : Version 2.25+
|
||||
- **Bash** : Version 4.0+
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
### 1. Installation de Docker
|
||||
|
||||
#### Ubuntu/Debian
|
||||
|
||||
```bash
|
||||
# Mettre à jour les paquets
|
||||
sudo apt update
|
||||
|
||||
# Installer les dépendances
|
||||
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
|
||||
|
||||
# Ajouter la clé GPG Docker
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
# Ajouter le repository Docker
|
||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
# Installer Docker
|
||||
sudo apt update
|
||||
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
|
||||
# Ajouter l'utilisateur au groupe docker
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
# Démarrer Docker
|
||||
sudo systemctl start docker
|
||||
sudo systemctl enable docker
|
||||
```
|
||||
|
||||
#### CentOS/RHEL
|
||||
|
||||
```bash
|
||||
# Installer les dépendances
|
||||
sudo yum install -y yum-utils
|
||||
|
||||
# Ajouter le repository Docker
|
||||
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||
|
||||
# Installer Docker
|
||||
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
|
||||
# Démarrer Docker
|
||||
sudo systemctl start docker
|
||||
sudo systemctl enable docker
|
||||
|
||||
# Ajouter l'utilisateur au groupe docker
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
### 2. Configuration SSH (Recommandé)
|
||||
|
||||
```bash
|
||||
# Générer une clé SSH
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_4nk -C "4nk-automation"
|
||||
|
||||
# Ajouter à l'agent SSH
|
||||
ssh-add ~/.ssh/id_ed25519_4nk
|
||||
|
||||
# Configurer Git pour utiliser la clé
|
||||
git config --global core.sshCommand "ssh -i ~/.ssh/id_ed25519_4nk"
|
||||
|
||||
# Afficher la clé publique pour Gitea
|
||||
cat ~/.ssh/id_ed25519_4nk.pub
|
||||
```
|
||||
|
||||
**Ajouter la clé publique à votre forge Git (Gitea/GitHub/GitLab) :**
|
||||
1. Aller sur Gitea > Settings > SSH Keys
|
||||
2. Coller la clé publique
|
||||
3. Cliquer sur "Add key"
|
||||
|
||||
### 3. Clonage du Repository
|
||||
|
||||
```bash
|
||||
# Cloner avec SSH (recommandé)
|
||||
git clone <REPO_SSH_URL>
|
||||
cd docv
|
||||
|
||||
# Ou avec HTTPS (si SSH non configuré)
|
||||
# git clone <REPO_HTTPS_URL>
|
||||
# cd docv
|
||||
```
|
||||
|
||||
### 4. Vérification de l'Installation
|
||||
|
||||
```bash
|
||||
# Vérifier Docker
|
||||
docker --version
|
||||
docker-compose --version
|
||||
|
||||
# Vérifier la connectivité à votre forge (si SSH)
|
||||
ssh -T git@github.com
|
||||
|
||||
# Vérifier les permissions
|
||||
ls -la
|
||||
```
|
||||
|
||||
## 🔧 Configuration Initiale
|
||||
|
||||
### 1. Configuration des Variables d'Environnement
|
||||
|
||||
```bash
|
||||
# Créer le fichier d'environnement
|
||||
cat > .env << EOF
|
||||
# Configuration projet
|
||||
PROJECT_NAME=docv
|
||||
NETWORK_NAME=4nk_node_btcnet
|
||||
|
||||
# Logs
|
||||
RUST_LOG=debug,bitcoincore_rpc=trace
|
||||
|
||||
# Bitcoin
|
||||
BITCOIN_COOKIE_PATH=/home/bitcoin/.bitcoin/signet/.cookie
|
||||
|
||||
# Synchronisation
|
||||
ENABLE_SYNC_TEST=1
|
||||
|
||||
# Ports
|
||||
TOR_PORTS=9050:9050,9051:9051
|
||||
BITCOIN_PORTS=38333:38333,18443:18443,29000:29000
|
||||
BLINDBIT_PORTS=8000:8000
|
||||
RELAY_1_PORTS=8090:8090,8091:8091
|
||||
RELAY_2_PORTS=8092:8090,8093:8091
|
||||
RELAY_3_PORTS=8094:8090,8095:8091
|
||||
EOF
|
||||
```
|
||||
|
||||
### 2. Configuration Bitcoin Core
|
||||
|
||||
```bash
|
||||
# Vérifier la configuration Bitcoin
|
||||
cat bitcoin/bitcoin.conf
|
||||
|
||||
# Modifier si nécessaire
|
||||
nano bitcoin/bitcoin.conf
|
||||
```
|
||||
|
||||
**Configuration recommandée :**
|
||||
```ini
|
||||
# Configuration Bitcoin Core Signet
|
||||
signet=1
|
||||
rpcuser=bitcoin
|
||||
rpcpassword=your_secure_password
|
||||
rpcbind=0.0.0.0
|
||||
rpcallowip=172.19.0.0/16
|
||||
zmqpubrawblock=tcp://0.0.0.0:29000
|
||||
zmqpubrawtx=tcp://0.0.0.0:29000
|
||||
txindex=1
|
||||
server=1
|
||||
listen=1
|
||||
```
|
||||
|
||||
### 3. Configuration Blindbit
|
||||
|
||||
```bash
|
||||
# Vérifier la configuration Blindbit
|
||||
cat blindbit/blindbit.toml
|
||||
|
||||
# Modifier si nécessaire
|
||||
nano blindbit/blindbit.toml
|
||||
```
|
||||
|
||||
**Configuration recommandée :**
|
||||
```toml
|
||||
# Configuration Blindbit
|
||||
host = "0.0.0.0:8000"
|
||||
chain = "signet"
|
||||
rpc_endpoint = "http://bitcoin:18443"
|
||||
cookie_path = "/home/bitcoin/.bitcoin/signet/.cookie"
|
||||
sync_start_height = 1
|
||||
max_parallel_tweak_computations = 4
|
||||
max_parallel_requests = 4
|
||||
```
|
||||
|
||||
### 4. Configuration des Relais
|
||||
|
||||
```bash
|
||||
# Vérifier les configurations des relais
|
||||
ls -la sdk_relay/.conf.docker.*
|
||||
|
||||
# Modifier si nécessaire
|
||||
nano sdk_relay/.conf.docker.relay1
|
||||
nano sdk_relay/.conf.docker.relay2
|
||||
nano sdk_relay/.conf.docker.relay3
|
||||
```
|
||||
|
||||
**Configuration recommandée pour chaque relay :**
|
||||
```ini
|
||||
core_url=http://bitcoin:18443
|
||||
core_wallet=relay_wallet
|
||||
ws_url=0.0.0.0:8090
|
||||
wallet_name=relay_wallet.json
|
||||
network=signet
|
||||
blindbit_url=http://blindbit:8000
|
||||
zmq_url=tcp://bitcoin:29000
|
||||
data_dir=.4nk
|
||||
cookie_path=/home/bitcoin/.4nk/bitcoin.cookie
|
||||
dev_mode=true
|
||||
standalone=false
|
||||
relay_id=relay-1 # Changer pour chaque relay
|
||||
```
|
||||
|
||||
## 🚀 Démarrage
|
||||
|
||||
### 1. Démarrage Complet
|
||||
|
||||
```bash
|
||||
# Démarrer tous les services
|
||||
./restart_4nk_node.sh
|
||||
|
||||
# Vérifier le statut
|
||||
docker ps
|
||||
```
|
||||
|
||||
### 2. Démarrage Séquentiel (Debug)
|
||||
|
||||
```bash
|
||||
# Démarrer Tor
|
||||
./restart_4nk_node.sh -t
|
||||
|
||||
# Démarrer Bitcoin Core
|
||||
./restart_4nk_node.sh -b
|
||||
|
||||
# Attendre la synchronisation Bitcoin (10-30 minutes)
|
||||
echo "Attendre la synchronisation Bitcoin..."
|
||||
docker logs bitcoin-signet | grep "progress"
|
||||
|
||||
# Démarrer Blindbit
|
||||
./restart_4nk_node.sh -l
|
||||
|
||||
# Démarrer les relais
|
||||
./restart_4nk_node.sh -r
|
||||
```
|
||||
|
||||
### 3. Vérification du Démarrage
|
||||
|
||||
```bash
|
||||
# Vérifier tous les services
|
||||
docker ps
|
||||
|
||||
# Vérifier les logs
|
||||
docker-compose logs --tail=50
|
||||
|
||||
# Vérifier la connectivité
|
||||
./test_final_sync.sh
|
||||
```
|
||||
|
||||
## 🧪 Tests Post-Installation
|
||||
|
||||
### 1. Tests de Connectivité
|
||||
|
||||
```bash
|
||||
# Test de base
|
||||
./test_final_sync.sh
|
||||
|
||||
# Test de synchronisation
|
||||
./test_sync_logs.sh
|
||||
|
||||
# Test des messages WebSocket
|
||||
python3 test_websocket_messages.py
|
||||
```
|
||||
|
||||
### 2. Tests de Performance
|
||||
|
||||
```bash
|
||||
# Vérifier l'utilisation des ressources
|
||||
docker stats
|
||||
|
||||
# Test de charge
|
||||
python3 test_websocket_messages.py --load-test
|
||||
|
||||
# Monitoring de la synchronisation
|
||||
./monitor_sync.sh
|
||||
```
|
||||
|
||||
### 3. Tests de Sécurité
|
||||
|
||||
```bash
|
||||
# Vérifier les ports exposés
|
||||
netstat -tlnp | grep -E "(18443|8000|9050|8090)"
|
||||
|
||||
# Vérifier les permissions
|
||||
ls -la sdk_relay/.conf*
|
||||
ls -la bitcoin/bitcoin.conf
|
||||
ls -la blindbit/blindbit.toml
|
||||
```
|
||||
|
||||
## 🔧 Configuration Avancée
|
||||
|
||||
### 1. Configuration Réseau
|
||||
|
||||
```bash
|
||||
# Créer un réseau Docker personnalisé
|
||||
docker network create 4nk-network --subnet=172.20.0.0/16
|
||||
|
||||
# Modifier docker-compose.yml
|
||||
sed -i 's/4nk_default/4nk-network/g' docker-compose.yml
|
||||
```
|
||||
|
||||
### 2. Configuration SSL/TLS
|
||||
|
||||
```bash
|
||||
# Générer un certificat auto-signé
|
||||
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
|
||||
|
||||
# Configurer nginx comme proxy SSL
|
||||
cat > nginx.conf << EOF
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name your-domain.com;
|
||||
|
||||
ssl_certificate cert.pem;
|
||||
ssl_certificate_key key.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8090;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host \$host;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### 3. Configuration de Pare-feu
|
||||
|
||||
```bash
|
||||
# Autoriser seulement les ports nécessaires
|
||||
sudo ufw allow 18443/tcp # Bitcoin Core RPC
|
||||
sudo ufw allow 8090/tcp # sdk_relay WebSocket
|
||||
sudo ufw allow 8000/tcp # Blindbit API
|
||||
sudo ufw enable
|
||||
|
||||
# Vérifier les règles
|
||||
sudo ufw status numbered
|
||||
```
|
||||
|
||||
## 🚨 Dépannage
|
||||
|
||||
### Problèmes Courants
|
||||
|
||||
#### 1. Docker Non Installé
|
||||
|
||||
```bash
|
||||
# Vérifier l'installation Docker
|
||||
docker --version
|
||||
|
||||
# Si non installé, suivre les étapes d'installation ci-dessus
|
||||
```
|
||||
|
||||
#### 2. Permissions Docker
|
||||
|
||||
```bash
|
||||
# Vérifier les permissions
|
||||
docker ps
|
||||
|
||||
# Si erreur de permission
|
||||
sudo usermod -aG docker $USER
|
||||
newgrp docker
|
||||
```
|
||||
|
||||
#### 3. Ports Déjà Utilisés
|
||||
|
||||
```bash
|
||||
# Vérifier les ports utilisés
|
||||
sudo netstat -tlnp | grep -E "(18443|8000|9050|8090)"
|
||||
|
||||
# Arrêter les services conflictuels
|
||||
sudo docker-compose down
|
||||
```
|
||||
|
||||
#### 4. Problèmes de Synchronisation Bitcoin
|
||||
|
||||
```bash
|
||||
# Vérifier les logs Bitcoin
|
||||
docker logs bitcoin-signet
|
||||
|
||||
# Vérifier l'espace disque
|
||||
df -h
|
||||
|
||||
# Redémarrer Bitcoin Core
|
||||
docker restart bitcoin-signet
|
||||
```
|
||||
|
||||
### Logs Utiles
|
||||
|
||||
```bash
|
||||
# Logs de tous les services
|
||||
docker-compose logs -f
|
||||
|
||||
# Logs d'un service spécifique
|
||||
docker logs bitcoin-signet
|
||||
docker logs blindbit-oracle
|
||||
docker logs sdk_relay_1
|
||||
|
||||
# Logs avec timestamps
|
||||
docker-compose logs -t
|
||||
|
||||
# Logs depuis une date
|
||||
docker-compose logs --since="2024-01-01T00:00:00"
|
||||
```
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### 1. Monitoring de Base
|
||||
|
||||
```bash
|
||||
# Statut des conteneurs
|
||||
docker ps
|
||||
|
||||
# Utilisation des ressources
|
||||
docker stats
|
||||
|
||||
# Espace disque
|
||||
docker system df
|
||||
```
|
||||
|
||||
### 2. Monitoring Avancé
|
||||
|
||||
```bash
|
||||
# Surveillance de la synchronisation
|
||||
./monitor_sync.sh
|
||||
|
||||
# Monitoring en continu
|
||||
while true; do
|
||||
echo "=== $(date) ==="
|
||||
docker stats --no-stream | grep -E "(sdk_relay|bitcoin)"
|
||||
sleep 30
|
||||
done
|
||||
```
|
||||
|
||||
### 3. Alertes
|
||||
|
||||
```bash
|
||||
# Script d'alerte simple
|
||||
cat > monitor_alert.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
if ! docker ps | grep -q "bitcoin-signet.*Up"; then
|
||||
echo "ALERTE: Bitcoin Core n'est pas en cours d'exécution!"
|
||||
# Ajouter notification (email, Slack, etc.)
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x monitor_alert.sh
|
||||
```
|
||||
|
||||
## 🔄 Mise à Jour
|
||||
|
||||
### 1. Mise à Jour de l'Infrastructure
|
||||
|
||||
```bash
|
||||
# Sauvegarder la configuration
|
||||
cp -r . ../4NK_node_backup_$(date +%Y%m%d)
|
||||
|
||||
# Mettre à jour le code
|
||||
git pull origin main
|
||||
|
||||
# Redémarrer les services
|
||||
./restart_4nk_node.sh
|
||||
```
|
||||
|
||||
### 2. Mise à Jour de Docker
|
||||
|
||||
```bash
|
||||
# Mettre à jour Docker
|
||||
sudo apt update
|
||||
sudo apt upgrade docker-ce docker-ce-cli containerd.io
|
||||
|
||||
# Redémarrer Docker
|
||||
sudo systemctl restart docker
|
||||
```
|
||||
|
||||
### 3. Mise à Jour des Images
|
||||
|
||||
```bash
|
||||
# Reconstruire les images
|
||||
docker-compose build --no-cache
|
||||
|
||||
# Redémarrer les services
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 📝 Checklist d'Installation
|
||||
|
||||
- [ ] Docker installé et configuré
|
||||
- [ ] Docker Compose installé
|
||||
- [ ] Clé SSH configurée pour Gitea
|
||||
- [ ] Repository cloné
|
||||
- [ ] Variables d'environnement configurées
|
||||
- [ ] Configurations Bitcoin Core vérifiées
|
||||
- [ ] Configurations Blindbit vérifiées
|
||||
- [ ] Configurations des relais vérifiées
|
||||
- [ ] Services démarrés avec succès
|
||||
- [ ] Tests de connectivité passés
|
||||
- [ ] Tests de synchronisation passés
|
||||
- [ ] Monitoring configuré
|
||||
- [ ] Pare-feu configuré (optionnel)
|
||||
- [ ] SSL/TLS configuré (optionnel)
|
||||
|
||||
## 🎉 Installation Terminée
|
||||
|
||||
Félicitations ! L'infrastructure docv est maintenant installée et configurée.
|
||||
|
||||
**Prochaines étapes :**
|
||||
1. Consulter le [Guide d'Utilisation](USAGE.md)
|
||||
2. Configurer les [Nœuds Externes](EXTERNAL_NODES.md)
|
||||
3. Tester la [Synchronisation](SYNCHRONIZATION.md)
|
||||
4. Configurer le [Monitoring](PERFORMANCE.md)
|
||||
|
||||
---
|
||||
@ -1,234 +0,0 @@
|
||||
# Checklist de Préparation Open Source - docv
|
||||
|
||||
Cette checklist détaille tous les éléments nécessaires pour préparer ce projet (docv) à une ouverture en open source.
|
||||
|
||||
## 📋 État Actuel du Projet
|
||||
|
||||
### ✅ **Complété (95%)**
|
||||
|
||||
#### 📚 Documentation
|
||||
- [x] **README.md** - Guide principal complet
|
||||
- [x] **docs/INSTALLATION.md** - Guide d'installation détaillé
|
||||
- [x] **docs/USAGE.md** - Guide d'utilisation quotidienne
|
||||
- [x] **docs/CONFIGURATION.md** - Guide de configuration avancée
|
||||
- [x] **docs/ARCHITECTURE.md** - Architecture technique complète
|
||||
- [x] **docs/API.md** - Documentation des APIs
|
||||
- [x] **docs/TESTING.md** - Guide des tests
|
||||
- [x] **docs/INDEX.md** - Index de la documentation
|
||||
- [x] **docs/QUICK_REFERENCE.md** - Référence rapide
|
||||
|
||||
#### 🧪 Tests
|
||||
- [x] **Structure organisée** - tests/unit, integration, connectivity, external
|
||||
- [x] **Scripts automatisés** - run_all_tests.sh, run_*_tests.sh
|
||||
- [x] **Tests de connectivité** - WebSocket, HTTP, RPC
|
||||
- [x] **Tests d'intégration** - Multi-relais, synchronisation
|
||||
- [x] **Tests externes** - dev3.4nkweb.com
|
||||
- [x] **Documentation des tests** - tests/README.md
|
||||
|
||||
#### 🔧 Infrastructure
|
||||
- [x] **Docker Compose** - Configuration complète
|
||||
- [x] **Healthchecks** - Pour tous les services
|
||||
- [x] **Scripts d'automatisation** - restart_4nk_node.sh
|
||||
- [x] **Monitoring** - Scripts de surveillance
|
||||
- [x] **Configuration externalisée** - Fichiers .conf
|
||||
|
||||
#### 🏗️ Architecture
|
||||
- [x] **Synchronisation mesh** - Entre relais
|
||||
- [x] **Cache de déduplication** - Messages
|
||||
- [x] **Découverte de nœuds** - Automatique et manuelle
|
||||
- [x] **Gestion d'erreurs** - Robuste
|
||||
- [x] **Logging structuré** - Avec rotation
|
||||
|
||||
### ⚠️ **À Compléter (5%)**
|
||||
|
||||
#### 📄 Fichiers de Licence et Contribution
|
||||
- [x] **LICENSE** - MIT License créé
|
||||
- [x] **CONTRIBUTING.md** - Guide de contribution créé
|
||||
- [x] **CHANGELOG.md** - Historique des versions créé
|
||||
- [x] **CODE_OF_CONDUCT.md** - Code de conduite créé
|
||||
- [x] **SECURITY.md** - Politique de sécurité créé
|
||||
|
||||
#### 🔄 CI/CD et Qualité
|
||||
- [x] **GitHub/Gitea Actions** - Workflow CI créé
|
||||
- [x] **Templates d'issues** - Bug report et feature request créés
|
||||
- [x] **Template de PR** - Pull request template créé
|
||||
- [x] **Release Guard** - Job `release-guard` et scripts présents
|
||||
|
||||
## 🎯 Checklist Finale
|
||||
|
||||
### 📋 **Phase 1 : Vérification Immédiate**
|
||||
|
||||
#### Audit de Sécurité
|
||||
- [ ] **Vérifier les secrets** - Pas de clés privées dans le code
|
||||
- [ ] **Vérifier les URLs** - Pas d'endpoints privés
|
||||
- [ ] **Vérifier les configurations** - Pas de données sensibles
|
||||
- [ ] **Vérifier les permissions** - Fichiers sensibles protégés
|
||||
|
||||
#### Vérification des Dépendances
|
||||
- [ ] **Versions des dépendances** - À jour et sécurisées
|
||||
- [ ] **Licences des dépendances** - Compatibles avec MIT
|
||||
- [ ] **Vulnérabilités** - Scan avec cargo audit
|
||||
- [ ] **Documentation des dépendances** - README mis à jour
|
||||
|
||||
#### Tests de Validation
|
||||
- [ ] **Tests complets** - Tous les tests passent
|
||||
- [ ] **Garde de release** - `RELEASE_TYPE=ci-verify scripts/release/guard.sh` OK
|
||||
- [ ] **Tests de sécurité** - Ajoutés et fonctionnels
|
||||
- [ ] **Tests de performance** - Ajoutés et fonctionnels
|
||||
- [ ] **Tests de compatibilité** - Multi-plateformes
|
||||
|
||||
### 📋 **Phase 2 : Préparation du Repository**
|
||||
|
||||
#### Repository Public
|
||||
- [ ] **Créer repository public** - Sur Gitea/GitHub/GitLab
|
||||
- [ ] **Configurer les branches** - main, develop, feature/*
|
||||
- [ ] **Configurer les protections** - Branch protection rules
|
||||
- [ ] **Configurer les labels** - bug, enhancement, documentation, etc.
|
||||
|
||||
#### Documentation Publique
|
||||
- [ ] **README public** - Version adaptée pour l'open source
|
||||
- [ ] **Documentation traduite** - En anglais si possible
|
||||
- [ ] **Exemples publics** - Sans données sensibles
|
||||
- [ ] **Guide de démarrage** - Pour les nouveaux contributeurs
|
||||
|
||||
#### Communication
|
||||
- [ ] **Annonce de l'ouverture** - Préparer la communication
|
||||
- [ ] **Support communautaire** - Canaux de discussion
|
||||
- [ ] **FAQ** - Questions fréquentes
|
||||
- [ ] **Roadmap** - Plan de développement
|
||||
|
||||
### 📋 **Phase 3 : Infrastructure Communautaire**
|
||||
|
||||
#### Outils de Collaboration
|
||||
- [ ] **Issues templates** - Bug report, feature request
|
||||
- [ ] **PR templates** - Pull request template
|
||||
- [ ] **Discussions** - Forum pour questions générales
|
||||
- [ ] **Wiki** - Documentation collaborative
|
||||
|
||||
#### Qualité du Code
|
||||
- [ ] **Linting** - Clippy, rustfmt configurés
|
||||
- [ ] **Tests automatisés** - CI/CD complet
|
||||
- [ ] **Coverage** - Couverture de tests > 80%
|
||||
- [ ] **Documentation** - Code auto-documenté
|
||||
|
||||
#### Monitoring et Support
|
||||
- [ ] **Monitoring** - Métriques publiques
|
||||
- [ ] **Alertes** - Notifications automatiques
|
||||
- [ ] **Support** - Canaux de support
|
||||
- [ ] **Maintenance** - Plan de maintenance
|
||||
|
||||
## 🚀 Plan d'Action Détaillé
|
||||
|
||||
### **Jour 1 : Audit et Nettoyage**
|
||||
```bash
|
||||
# Audit de sécurité
|
||||
./scripts/security_audit.sh
|
||||
|
||||
# Nettoyage des secrets
|
||||
./scripts/clean_secrets.sh
|
||||
|
||||
# Vérification des dépendances
|
||||
cargo audit
|
||||
cargo update
|
||||
```
|
||||
|
||||
### **Jour 2 : Tests et Validation**
|
||||
```bash
|
||||
# Tests complets
|
||||
./tests/run_all_tests.sh
|
||||
|
||||
# Tests de sécurité
|
||||
./tests/run_security_tests.sh
|
||||
|
||||
# Tests de performance
|
||||
./tests/run_performance_tests.sh
|
||||
```
|
||||
|
||||
### **Jour 3 : Documentation Finale**
|
||||
```bash
|
||||
# Vérification de la documentation
|
||||
./scripts/check_documentation.sh
|
||||
|
||||
# Génération de la documentation
|
||||
./scripts/generate_docs.sh
|
||||
|
||||
# Validation des liens
|
||||
./scripts/validate_links.sh
|
||||
```
|
||||
|
||||
### **Jour 4 : Repository Public**
|
||||
```bash
|
||||
# Création du repository public
|
||||
# Configuration des branches
|
||||
# Configuration des protections
|
||||
# Upload du code
|
||||
```
|
||||
|
||||
### **Jour 5 : Communication et Support**
|
||||
```bash
|
||||
# Préparation de l'annonce
|
||||
# Configuration des canaux de support
|
||||
# Test de l'infrastructure
|
||||
# Validation finale
|
||||
```
|
||||
|
||||
## 📊 Métriques de Préparation
|
||||
|
||||
### **Qualité du Code**
|
||||
- **Couverture de tests** : 85% ✅
|
||||
- **Documentation** : 95% ✅
|
||||
- **Linting** : 90% ✅
|
||||
- **Sécurité** : 85% ✅
|
||||
|
||||
### **Infrastructure**
|
||||
- **Docker** : 100% ✅
|
||||
- **CI/CD** : 90% ✅
|
||||
- **Monitoring** : 80% ✅
|
||||
- **Tests** : 90% ✅
|
||||
|
||||
### **Documentation**
|
||||
- **README** : 100% ✅
|
||||
- **Guides techniques** : 95% ✅
|
||||
- **API** : 90% ✅
|
||||
- **Exemples** : 85% ✅
|
||||
|
||||
### **Communauté**
|
||||
- **Licence** : 100% ✅
|
||||
- **Contribution** : 100% ✅
|
||||
- **Code de conduite** : 100% ✅
|
||||
- **Sécurité** : 100% ✅
|
||||
|
||||
## 🎯 Score Global : 92/100
|
||||
|
||||
### **Points Forts**
|
||||
- ✅ Documentation exceptionnelle
|
||||
- ✅ Tests bien organisés
|
||||
- ✅ Infrastructure Docker robuste
|
||||
- ✅ Architecture claire
|
||||
- ✅ Scripts d'automatisation
|
||||
|
||||
### **Points d'Amélioration**
|
||||
- ⚠️ Traduction en anglais (optionnel)
|
||||
- ⚠️ Tests de sécurité supplémentaires
|
||||
- ⚠️ Monitoring avancé
|
||||
- ⚠️ Exemples supplémentaires
|
||||
|
||||
## 🚀 Recommandation
|
||||
|
||||
**Le projet est PRÊT pour l'open source !**
|
||||
|
||||
### **Actions Immédiates (1-2 jours)**
|
||||
1. Audit de sécurité final
|
||||
2. Tests de validation complets
|
||||
3. Création du repository public
|
||||
4. Communication de l'ouverture
|
||||
|
||||
### **Actions Post-Ouverture (1-2 semaines)**
|
||||
1. Support de la communauté
|
||||
2. Amélioration continue
|
||||
3. Feedback et itération
|
||||
4. Évolution du projet
|
||||
|
||||
---
|
||||
|
||||
**Le projet a une base technique et documentaire excellente qui facilitera grandement son adoption par la communauté open source !** 🌟
|
||||
@ -1,494 +0,0 @@
|
||||
# ⚡ Référence Rapide - docv
|
||||
|
||||
Référence rapide des commandes essentielles pour l'infrastructure docv.
|
||||
|
||||
## 🚀 Démarrage
|
||||
|
||||
### Démarrage Complet
|
||||
```bash
|
||||
# Démarrer tous les services
|
||||
./restart_4nk_node.sh
|
||||
|
||||
# Vérifier le statut
|
||||
docker ps
|
||||
```
|
||||
|
||||
### Démarrage Séquentiel
|
||||
```bash
|
||||
# Démarrer Tor
|
||||
./restart_4nk_node.sh -t
|
||||
|
||||
# Démarrer Bitcoin Core
|
||||
./restart_4nk_node.sh -b
|
||||
|
||||
# Démarrer Blindbit
|
||||
./restart_4nk_node.sh -l
|
||||
|
||||
# Démarrer les relais
|
||||
./restart_4nk_node.sh -r
|
||||
```
|
||||
|
||||
### Options du Script de Redémarrage
|
||||
```bash
|
||||
./restart_4nk_node.sh -h # Aide
|
||||
./restart_4nk_node.sh -s # Arrêter
|
||||
./restart_4nk_node.sh -c # Nettoyer
|
||||
./restart_4nk_node.sh -n # Créer réseau
|
||||
./restart_4nk_node.sh -t # Démarrer Tor
|
||||
./restart_4nk_node.sh -b # Démarrer Bitcoin
|
||||
./restart_4nk_node.sh -l # Démarrer Blindbit
|
||||
./restart_4nk_node.sh -r # Démarrer relais
|
||||
./restart_4nk_node.sh -v # Vérifier statut
|
||||
```
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### Statut des Services
|
||||
```bash
|
||||
# Statut de tous les services
|
||||
docker ps
|
||||
|
||||
# Statut avec format personnalisé
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
|
||||
# Utilisation des ressources
|
||||
docker stats
|
||||
|
||||
# Espace disque
|
||||
docker system df
|
||||
```
|
||||
|
||||
### Logs
|
||||
```bash
|
||||
# Logs de tous les services
|
||||
docker-compose logs -f
|
||||
|
||||
# Logs d'un service spécifique
|
||||
docker logs bitcoin-signet
|
||||
docker logs blindbit-oracle
|
||||
docker logs sdk_relay_1
|
||||
|
||||
# Logs avec timestamps
|
||||
docker-compose logs -t
|
||||
|
||||
# Logs des 100 dernières lignes
|
||||
docker-compose logs --tail=100
|
||||
|
||||
# Logs depuis une date
|
||||
docker-compose logs --since="2024-01-01T00:00:00"
|
||||
```
|
||||
|
||||
### Surveillance de la Synchronisation
|
||||
```bash
|
||||
# Surveillance en temps réel
|
||||
./monitor_sync.sh
|
||||
|
||||
# Test de synchronisation
|
||||
./test_sync_logs.sh
|
||||
|
||||
# Test de synchronisation forcé
|
||||
./test_sync_logs.sh force
|
||||
|
||||
# Test de synchronisation en continu
|
||||
./test_sync_logs.sh continuous
|
||||
```
|
||||
|
||||
## 🧪 Tests
|
||||
|
||||
### Tests de Base
|
||||
```bash
|
||||
# Test de connectivité complet
|
||||
./test_final_sync.sh
|
||||
|
||||
# Test de synchronisation
|
||||
./test_sync_logs.sh
|
||||
|
||||
# Test des messages WebSocket
|
||||
python3 test_websocket_messages.py
|
||||
|
||||
# Test des 3 relais
|
||||
./test_3_relays.sh
|
||||
```
|
||||
|
||||
### Tests de Performance
|
||||
```bash
|
||||
# Test de charge WebSocket
|
||||
python3 test_websocket_messages.py --load-test
|
||||
|
||||
# Test de connectivité multiple
|
||||
netstat -tlnp | grep -E "(8090|8092|8094)"
|
||||
|
||||
# Test de performance
|
||||
docker stats --no-stream
|
||||
```
|
||||
|
||||
### Tests de Sécurité
|
||||
```bash
|
||||
# Vérifier les ports exposés
|
||||
netstat -tuln | grep -E "(8090|8092|8094)"
|
||||
|
||||
# Vérifier les logs d'accès
|
||||
docker logs sdk_relay_1 | grep -E "(ERROR|WARN)" | tail -20
|
||||
|
||||
# Vérifier l'utilisation des ressources
|
||||
docker stats --no-stream | grep sdk_relay
|
||||
```
|
||||
|
||||
## 🔗 Connexion aux Services
|
||||
|
||||
### Bitcoin Core RPC
|
||||
```bash
|
||||
# Connexion via curl
|
||||
curl -u bitcoin:your_password --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockchaininfo", "params": []}' -H 'content-type: text/plain;' http://localhost:18443/
|
||||
|
||||
# Connexion via bitcoin-cli
|
||||
docker exec bitcoin-signet bitcoin-cli -signet getblockchaininfo
|
||||
|
||||
# Vérifier la synchronisation
|
||||
docker exec bitcoin-signet bitcoin-cli -signet getblockchaininfo | jq '.verificationprogress'
|
||||
```
|
||||
|
||||
### Blindbit API
|
||||
```bash
|
||||
# Test de connectivité
|
||||
curl -s http://localhost:8000/
|
||||
|
||||
# Vérifier le statut
|
||||
curl -s http://localhost:8000/status
|
||||
|
||||
# Obtenir des filtres
|
||||
curl -s http://localhost:8000/filters
|
||||
```
|
||||
|
||||
### sdk_relay WebSocket
|
||||
```bash
|
||||
# Test de connectivité WebSocket
|
||||
curl -v -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: test" http://localhost:8090/
|
||||
|
||||
# Test avec wscat (si installé)
|
||||
wscat -c ws://localhost:8090
|
||||
|
||||
# Test avec Python
|
||||
python3 test_websocket_messages.py
|
||||
```
|
||||
|
||||
## 🌐 Gestion des Nœuds Externes
|
||||
|
||||
### Administration des Nœuds
|
||||
```bash
|
||||
# Ajouter un nœud externe
|
||||
./add_external_node.sh add external-relay-1 external-relay-1.example.com:8090
|
||||
|
||||
# Lister les nœuds configurés
|
||||
./add_external_node.sh list
|
||||
|
||||
# Tester la connectivité
|
||||
./add_external_node.sh test external-relay-1
|
||||
|
||||
# Supprimer un nœud
|
||||
./add_external_node.sh remove external-relay-1
|
||||
|
||||
# Valider une adresse
|
||||
./add_external_node.sh validate 192.168.1.100:8090
|
||||
```
|
||||
|
||||
### Configuration Multi-Sites
|
||||
```bash
|
||||
# Site principal
|
||||
./add_external_node.sh add site-paris-1 paris-relay-1.4nk.net:8090
|
||||
./add_external_node.sh add site-paris-2 paris-relay-2.4nk.net:8090
|
||||
|
||||
# Site secondaire
|
||||
./add_external_node.sh add site-lyon-1 lyon-relay-1.4nk.net:8090
|
||||
./add_external_node.sh add site-lyon-2 lyon-relay-2.4nk.net:8090
|
||||
|
||||
# Site de backup
|
||||
./add_external_node.sh add backup-1 backup-relay-1.4nk.net:8090
|
||||
```
|
||||
|
||||
### Test d'Intégration
|
||||
```bash
|
||||
# Test d'intégration complet
|
||||
./test_integration_dev3.sh
|
||||
|
||||
# Test de connectivité dev3
|
||||
python3 test_dev3_simple.py
|
||||
|
||||
# Test de connectivité avancé
|
||||
python3 test_dev3_connectivity.py
|
||||
```
|
||||
|
||||
## 🔧 Configuration et Maintenance
|
||||
|
||||
### Modification de Configuration
|
||||
```bash
|
||||
# Modifier la configuration Bitcoin Core
|
||||
sudo docker-compose down
|
||||
nano bitcoin/bitcoin.conf
|
||||
sudo docker-compose up -d bitcoin
|
||||
|
||||
# Modifier la configuration Blindbit
|
||||
nano blindbit/blindbit.toml
|
||||
sudo docker-compose restart blindbit
|
||||
|
||||
# Modifier la configuration des relais
|
||||
nano sdk_relay/.conf.docker.relay1
|
||||
sudo docker-compose restart sdk_relay_1
|
||||
```
|
||||
|
||||
### Redémarrage des Services
|
||||
```bash
|
||||
# Redémarrage complet
|
||||
./restart_4nk_node.sh
|
||||
|
||||
# Redémarrage d'un service spécifique
|
||||
docker-compose restart bitcoin
|
||||
docker-compose restart blindbit
|
||||
docker-compose restart sdk_relay_1
|
||||
|
||||
# Redémarrage avec reconstruction
|
||||
docker-compose down
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Sauvegarde et Restauration
|
||||
```bash
|
||||
# Sauvegarde des données
|
||||
docker exec bitcoin-signet tar czf /tmp/bitcoin-backup.tar.gz /home/bitcoin/.bitcoin
|
||||
docker cp bitcoin-signet:/tmp/bitcoin-backup.tar.gz ./backup/
|
||||
|
||||
# Sauvegarde des configurations
|
||||
tar czf config-backup.tar.gz sdk_relay/.conf* external_nodes.conf
|
||||
|
||||
# Restauration
|
||||
docker cp ./backup/bitcoin-backup.tar.gz bitcoin-signet:/tmp/
|
||||
docker exec bitcoin-signet tar xzf /tmp/bitcoin-backup.tar.gz -C /
|
||||
```
|
||||
|
||||
## 🚨 Dépannage
|
||||
|
||||
### Problèmes Courants
|
||||
```bash
|
||||
# Service ne démarre pas
|
||||
docker logs <service_name>
|
||||
docker exec <service_name> cat /path/to/config
|
||||
docker restart <service_name>
|
||||
|
||||
# Problèmes de connectivité
|
||||
docker exec <service_name> ping <target>
|
||||
docker exec <service_name> nslookup <target>
|
||||
docker exec <service_name> nc -z <target> <port>
|
||||
|
||||
# Problèmes de synchronisation
|
||||
docker logs sdk_relay_1 | grep -E "(Sync|Relay|Mesh)"
|
||||
docker restart sdk_relay_1 sdk_relay_2 sdk_relay_3
|
||||
./test_sync_logs.sh force
|
||||
```
|
||||
|
||||
### Outils de Debug
|
||||
```bash
|
||||
# Debug du container sdk_relay
|
||||
./sdk_relay/debug_container.sh
|
||||
|
||||
# Test du healthcheck
|
||||
./sdk_relay/test_healthcheck.sh
|
||||
|
||||
# Test de connectivité
|
||||
./sdk_relay/test_connectivity.sh
|
||||
|
||||
# Test simple
|
||||
./sdk_relay/test_simple.sh
|
||||
```
|
||||
|
||||
### Logs de Debug
|
||||
```bash
|
||||
# Logs détaillés
|
||||
docker-compose logs -f --tail=100
|
||||
|
||||
# Logs d'un service spécifique
|
||||
docker logs <service_name> -f
|
||||
|
||||
# Logs avec timestamps
|
||||
docker-compose logs -t
|
||||
|
||||
# Logs depuis une date
|
||||
docker-compose logs --since="2024-01-01T00:00:00"
|
||||
```
|
||||
|
||||
## 🔒 Sécurité
|
||||
|
||||
### Vérification de Sécurité
|
||||
```bash
|
||||
# Vérifier les ports exposés
|
||||
netstat -tuln | grep -E "(8090|8092|8094)"
|
||||
|
||||
# Vérifier les permissions
|
||||
ls -la sdk_relay/.conf*
|
||||
ls -la bitcoin/bitcoin.conf
|
||||
ls -la blindbit/blindbit.toml
|
||||
|
||||
# Vérifier les logs de sécurité
|
||||
docker logs sdk_relay_1 | grep -E "(ERROR|WARN|SECURITY)" | tail -20
|
||||
```
|
||||
|
||||
### Configuration de Pare-feu
|
||||
```bash
|
||||
# Autoriser les ports nécessaires
|
||||
sudo ufw allow 18443/tcp # Bitcoin Core RPC
|
||||
sudo ufw allow 8090/tcp # sdk_relay WebSocket
|
||||
sudo ufw allow 8000/tcp # Blindbit API
|
||||
sudo ufw enable
|
||||
|
||||
# Vérifier les règles
|
||||
sudo ufw status numbered
|
||||
```
|
||||
|
||||
## 📈 Performance
|
||||
|
||||
### Optimisation
|
||||
```bash
|
||||
# Limiter l'utilisation CPU
|
||||
docker-compose up -d --scale bitcoin=1
|
||||
|
||||
# Optimiser la mémoire
|
||||
docker stats --no-stream | grep sdk_relay
|
||||
|
||||
# Nettoyer l'espace disque
|
||||
docker system prune -f
|
||||
```
|
||||
|
||||
### Monitoring de Performance
|
||||
```bash
|
||||
# Surveillance des ressources
|
||||
docker stats
|
||||
|
||||
# Surveillance des connexions
|
||||
netstat -an | grep :8090 | wc -l
|
||||
|
||||
# Surveillance de l'espace disque
|
||||
df -h
|
||||
```
|
||||
|
||||
### Tests de Charge
|
||||
```bash
|
||||
# Test de charge simple
|
||||
for i in {1..50}; do
|
||||
python3 test_websocket_messages.py &
|
||||
sleep 0.1
|
||||
done
|
||||
wait
|
||||
|
||||
# Test de charge avancé
|
||||
python3 test_websocket_messages.py --load-test --duration=300
|
||||
```
|
||||
|
||||
## 🔄 Maintenance
|
||||
|
||||
### Nettoyage
|
||||
```bash
|
||||
# Nettoyer les conteneurs arrêtés
|
||||
docker container prune -f
|
||||
|
||||
# Nettoyer les images non utilisées
|
||||
docker image prune -f
|
||||
|
||||
# Nettoyer les volumes non utilisés
|
||||
docker volume prune -f
|
||||
|
||||
# Nettoyer tout
|
||||
docker system prune -a -f
|
||||
```
|
||||
|
||||
### Mise à Jour
|
||||
```bash
|
||||
# Mise à jour de l'infrastructure
|
||||
git pull origin main
|
||||
./restart_4nk_node.sh
|
||||
|
||||
# Mise à jour des images
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Sauvegarde Automatique
|
||||
```bash
|
||||
# Script de sauvegarde
|
||||
cat > backup_4nk.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_DIR="/backup/4nk_node_$DATE"
|
||||
mkdir -p $BACKUP_DIR
|
||||
cp -r sdk_relay/.conf* $BACKUP_DIR/
|
||||
cp external_nodes.conf $BACKUP_DIR/
|
||||
docker exec bitcoin-signet tar czf /tmp/bitcoin-backup.tar.gz /home/bitcoin/.bitcoin
|
||||
docker cp bitcoin-signet:/tmp/bitcoin-backup.tar.gz $BACKUP_DIR/
|
||||
find /backup -name "4nk_node_*" -type d -mtime +7 -exec rm -rf {} \;
|
||||
echo "Sauvegarde terminée: $BACKUP_DIR"
|
||||
EOF
|
||||
|
||||
chmod +x backup_4nk.sh
|
||||
|
||||
# Ajouter au cron
|
||||
echo "0 2 * * * /path/to/backup_4nk.sh" | crontab -
|
||||
```
|
||||
|
||||
## 📋 Checklist Quotidienne
|
||||
|
||||
### Démarrage
|
||||
- [ ] Services démarrés et fonctionnels
|
||||
- [ ] Bitcoin Core synchronisé
|
||||
- [ ] Relais connectés et synchronisés
|
||||
- [ ] Tests de connectivité passés
|
||||
|
||||
### Surveillance
|
||||
- [ ] Logs vérifiés (pas d'erreurs critiques)
|
||||
- [ ] Ressources système OK
|
||||
- [ ] Monitoring actif
|
||||
- [ ] Sauvegarde effectuée (si nécessaire)
|
||||
|
||||
### Maintenance
|
||||
- [ ] Nettoyage effectué
|
||||
- [ ] Mise à jour appliquée (si nécessaire)
|
||||
- [ ] Configuration vérifiée
|
||||
- [ ] Sécurité contrôlée
|
||||
|
||||
## 🎯 Commandes Essentielles
|
||||
|
||||
### Démarrage Rapide
|
||||
```bash
|
||||
./restart_4nk_node.sh
|
||||
docker ps
|
||||
./test_final_sync.sh
|
||||
```
|
||||
|
||||
### Monitoring Rapide
|
||||
```bash
|
||||
docker ps
|
||||
docker-compose logs -f
|
||||
./monitor_sync.sh
|
||||
```
|
||||
|
||||
### Test Rapide
|
||||
```bash
|
||||
./test_final_sync.sh
|
||||
./test_sync_logs.sh
|
||||
python3 test_websocket_messages.py
|
||||
```
|
||||
|
||||
### Dépannage Rapide
|
||||
```bash
|
||||
docker logs <service_name>
|
||||
docker restart <service_name>
|
||||
./test_sync_logs.sh force
|
||||
```
|
||||
|
||||
### Arrêt Propre
|
||||
```bash
|
||||
docker-compose down
|
||||
docker system prune -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,361 +0,0 @@
|
||||
# Plan de Release Open Source - docv
|
||||
|
||||
## 🚀 Vue d'Ensemble
|
||||
|
||||
Ce document détaille le plan de lancement open source du projet docv sur votre forge (ex: Gitea/GitHub/GitLab).
|
||||
|
||||
### **Objectifs**
|
||||
- Lancer docv en open source avec succès
|
||||
- Attirer une communauté de contributeurs
|
||||
- Établir une base solide pour le développement futur
|
||||
- Positionner le projet dans l'écosystème Bitcoin
|
||||
|
||||
### **Date Cible**
|
||||
**Lancement : Janvier 2025**
|
||||
|
||||
## 📋 Phase 1 : Préparation Finale (1-2 semaines)
|
||||
|
||||
### **Configuration Gitea**
|
||||
|
||||
#### 1. **Repository Public**
|
||||
```bash
|
||||
# Actions à effectuer sur git.4nkweb.com
|
||||
- [ ] Rendre le repository public
|
||||
- [ ] Configurer les permissions d'accès
|
||||
- [ ] Activer les fonctionnalités communautaires
|
||||
```
|
||||
|
||||
#### 2. **Templates et Workflows**
|
||||
```bash
|
||||
# Vérifier l'activation des templates
|
||||
- [ ] Templates d'issues fonctionnels
|
||||
- [ ] Template de pull request actif
|
||||
- [ ] Workflow CI/CD configuré
|
||||
- [ ] Job CI `release-guard` actif (tests/doc/build/version/changelog)
|
||||
- [ ] Labels et milestones créés
|
||||
```
|
||||
|
||||
#### 3. **Documentation Publique**
|
||||
```bash
|
||||
# Finaliser la documentation
|
||||
- [ ] README.md optimisé pour l'open source
|
||||
- [ ] Documentation traduite en anglais (optionnel)
|
||||
- [ ] Exemples et tutoriels créés
|
||||
- [ ] FAQ préparée
|
||||
```
|
||||
|
||||
### **Tests de Validation**
|
||||
|
||||
#### 1. **Tests Complets**
|
||||
```bash
|
||||
# Exécuter tous les tests
|
||||
./tests/run_all_tests.sh
|
||||
|
||||
# Tests spécifiques
|
||||
./tests/run_connectivity_tests.sh
|
||||
./tests/run_external_tests.sh
|
||||
```
|
||||
|
||||
#### 2. **Tests de Déploiement**
|
||||
```bash
|
||||
# Test de déploiement complet
|
||||
./restart_4nk_node.sh
|
||||
|
||||
# Vérification des services
|
||||
docker ps
|
||||
docker logs bitcoin-signet
|
||||
docker logs blindbit-oracle
|
||||
docker logs sdk_relay_1
|
||||
```
|
||||
|
||||
#### 3. **Tests de Documentation**
|
||||
```bash
|
||||
# Vérifier les liens
|
||||
find docs/ -name "*.md" -exec grep -l "\[.*\](" {} \;
|
||||
|
||||
# Valider la structure
|
||||
ls -la docs/
|
||||
ls -la tests/
|
||||
|
||||
# Vérifier les obligations de release (local)
|
||||
RELEASE_TYPE=ci-verify scripts/release/guard.sh
|
||||
```
|
||||
|
||||
## 📋 Phase 2 : Communication et Marketing (1 semaine)
|
||||
|
||||
### **Annonce Officielle**
|
||||
|
||||
#### 1. **Communiqué de Presse**
|
||||
```markdown
|
||||
# Titre : docv - Infrastructure Open Source pour les Paiements Silencieux Bitcoin
|
||||
|
||||
## Résumé
|
||||
docv annonce le lancement en open source de son infrastructure complète pour les paiements silencieux Bitcoin. Cette solution Docker offre une implémentation complète avec Bitcoin Core, Blindbit, et un système de relais synchronisés.
|
||||
|
||||
## Points Clés
|
||||
- Infrastructure Docker complète
|
||||
- Support des paiements silencieux Bitcoin
|
||||
- Synchronisation mesh entre relais
|
||||
- Documentation technique exhaustive
|
||||
- Communauté open source
|
||||
|
||||
## Contact
|
||||
- Repository : https://git.4nkweb.com/4nk/4NK_node
|
||||
- Documentation : https://git.4nkweb.com/4nk/4NK_node/src/branch/main/docs
|
||||
- Support : support@4nkweb.com
|
||||
```
|
||||
|
||||
#### 2. **Canaux de Communication**
|
||||
```bash
|
||||
# Canaux à utiliser
|
||||
- [ ] Blog technique 4NK
|
||||
- [ ] Reddit r/Bitcoin, r/cryptocurrency
|
||||
- [ ] Twitter/X @4nkweb
|
||||
- [ ] LinkedIn 4NK
|
||||
- [ ] Forums Bitcoin (Bitcointalk)
|
||||
- [ ] Discord/Telegram Bitcoin
|
||||
- [ ] Podcasts techniques
|
||||
```
|
||||
|
||||
### **Contenu Marketing**
|
||||
|
||||
#### 1. **Vidéo de Présentation**
|
||||
```bash
|
||||
# Script de vidéo (5-10 minutes)
|
||||
- Introduction au projet
|
||||
- Démonstration de l'installation
|
||||
- Showcase des fonctionnalités
|
||||
- Appel à contribution
|
||||
```
|
||||
|
||||
#### 2. **Infographie**
|
||||
```bash
|
||||
# Éléments à inclure
|
||||
- Architecture du système
|
||||
- Flux de données
|
||||
- Avantages des paiements silencieux
|
||||
- Statistiques du projet
|
||||
```
|
||||
|
||||
#### 3. **Article Technique**
|
||||
```bash
|
||||
# Article pour blogs techniques
|
||||
- "Comment implémenter les paiements silencieux Bitcoin"
|
||||
- "Architecture d'une infrastructure Bitcoin moderne"
|
||||
- "Synchronisation mesh pour les relais Bitcoin"
|
||||
```
|
||||
|
||||
## 📋 Phase 3 : Lancement (1 jour)
|
||||
|
||||
### **Checklist de Lancement**
|
||||
|
||||
#### 1. **Pré-lancement (Jour J-1)**
|
||||
```bash
|
||||
# Vérifications finales
|
||||
- [ ] Tous les tests passent
|
||||
- [ ] Documentation à jour
|
||||
- [ ] Repository public configuré
|
||||
- [ ] Templates activés
|
||||
- [ ] Équipe de support prête
|
||||
```
|
||||
|
||||
#### 2. **Lancement (Jour J)**
|
||||
```bash
|
||||
# Actions de lancement
|
||||
- [ ] Publier le communiqué de presse
|
||||
- [ ] Poster sur les réseaux sociaux
|
||||
- [ ] Envoyer les annonces
|
||||
- [ ] Activer le support communautaire
|
||||
- [ ] Monitorer les réactions
|
||||
|
||||
# Release (modèle de commandes)
|
||||
git add -A && git commit -m "chore(release): 2025.08"
|
||||
git tag -a v2025.08 -m "release 2025.08"
|
||||
git push origin HEAD && git push origin v2025.08
|
||||
```
|
||||
|
||||
#### 3. **Post-lancement (Jour J+1)**
|
||||
```bash
|
||||
# Suivi et support
|
||||
- [ ] Répondre aux questions
|
||||
- [ ] Guider les premiers contributeurs
|
||||
- [ ] Collecter les retours
|
||||
- [ ] Ajuster la documentation si nécessaire
|
||||
```
|
||||
|
||||
## 📋 Phase 4 : Support Communautaire (2-4 semaines)
|
||||
|
||||
### **Équipe de Support**
|
||||
|
||||
#### 1. **Rôles et Responsabilités**
|
||||
```bash
|
||||
# Équipe de support
|
||||
- [ ] Maintainer principal : Révisions de code, releases
|
||||
- [ ] Support technique : Questions, bugs, documentation
|
||||
- [ ] Community manager : Engagement, modération
|
||||
- [ ] Security team : Vulnérabilités, audits
|
||||
```
|
||||
|
||||
#### 2. **Canaux de Support**
|
||||
```bash
|
||||
# Canaux à mettre en place
|
||||
- [ ] Issues Gitea : Bugs et fonctionnalités
|
||||
- [ ] Discussions Gitea : Questions générales
|
||||
- [ ] Email : support@4nkweb.com
|
||||
- [ ] Discord/Telegram : Support en temps réel
|
||||
- [ ] Documentation : Guides et tutoriels
|
||||
```
|
||||
|
||||
### **Gestion des Contributions**
|
||||
|
||||
#### 1. **Processus de Review**
|
||||
```bash
|
||||
# Workflow de contribution
|
||||
1. Issue créée ou PR soumise
|
||||
2. Review automatique (CI/CD)
|
||||
3. Review manuelle par maintainer
|
||||
4. Tests et validation
|
||||
5. Merge et release
|
||||
```
|
||||
|
||||
#### 2. **Standards de Qualité**
|
||||
```bash
|
||||
# Critères de qualité
|
||||
- [ ] Code conforme aux standards
|
||||
- [ ] Tests ajoutés/modifiés
|
||||
- [ ] Documentation mise à jour
|
||||
- [ ] Pas de régression
|
||||
- [ ] Performance acceptable
|
||||
```
|
||||
|
||||
## 📋 Phase 5 : Évolution et Maintenance (Ongoing)
|
||||
|
||||
### **Roadmap de Développement**
|
||||
|
||||
#### 1. **Court terme (1-3 mois)**
|
||||
```bash
|
||||
# Fonctionnalités prioritaires
|
||||
- [ ] Amélioration de la documentation
|
||||
- [ ] Tests de performance
|
||||
- [ ] Optimisations de sécurité
|
||||
- [ ] Support de nouveaux réseaux Bitcoin
|
||||
- [ ] Interface utilisateur web
|
||||
```
|
||||
|
||||
#### 2. **Moyen terme (3-6 mois)**
|
||||
```bash
|
||||
# Évolutions majeures
|
||||
- [ ] Support Lightning Network
|
||||
- [ ] API REST complète
|
||||
- [ ] Monitoring avancé
|
||||
- [ ] Déploiement cloud
|
||||
- [ ] Intégrations tierces
|
||||
```
|
||||
|
||||
#### 3. **Long terme (6-12 mois)**
|
||||
```bash
|
||||
# Vision stratégique
|
||||
- [ ] Écosystème complet
|
||||
- [ ] Marketplace d'extensions
|
||||
- [ ] Support multi-blockchains
|
||||
- [ ] IA et automatisation
|
||||
- [ ] Écosystème de développeurs
|
||||
```
|
||||
|
||||
### **Métriques de Succès**
|
||||
|
||||
#### 1. **Métriques Techniques**
|
||||
```bash
|
||||
# KPIs techniques
|
||||
- [ ] Nombre de stars/forks
|
||||
- [ ] Nombre de contributeurs
|
||||
- [ ] Taux de résolution des issues
|
||||
- [ ] Temps de réponse aux PR
|
||||
- [ ] Couverture de tests
|
||||
```
|
||||
|
||||
#### 2. **Métriques Communautaires**
|
||||
```bash
|
||||
# KPIs communautaires
|
||||
- [ ] Nombre d'utilisateurs actifs
|
||||
- [ ] Engagement sur les discussions
|
||||
- [ ] Qualité des contributions
|
||||
- [ ] Satisfaction utilisateurs
|
||||
- [ ] Adoption par d'autres projets
|
||||
```
|
||||
|
||||
## 🎯 Plan d'Action Détaillé
|
||||
|
||||
### **Semaine 1 : Finalisation**
|
||||
- [ ] Configuration Gitea complète
|
||||
- [ ] Tests de validation
|
||||
- [ ] Préparation communication
|
||||
|
||||
### **Semaine 2 : Communication**
|
||||
- [ ] Rédaction communiqué
|
||||
- [ ] Création contenu marketing
|
||||
- [ ] Préparation équipe support
|
||||
|
||||
### **Semaine 3 : Lancement**
|
||||
- [ ] Lancement officiel
|
||||
- [ ] Support communautaire
|
||||
- [ ] Monitoring et ajustements
|
||||
|
||||
### **Semaine 4+ : Évolution**
|
||||
- [ ] Gestion continue
|
||||
- [ ] Améliorations
|
||||
- [ ] Planification roadmap
|
||||
|
||||
## 📊 Budget et Ressources
|
||||
|
||||
### **Ressources Humaines**
|
||||
- **Maintainer principal** : 20h/semaine
|
||||
- **Support technique** : 15h/semaine
|
||||
- **Community manager** : 10h/semaine
|
||||
- **Security team** : 5h/semaine
|
||||
|
||||
### **Ressources Techniques**
|
||||
- **Infrastructure Gitea** : Déjà en place
|
||||
- **CI/CD** : Déjà configuré
|
||||
- **Monitoring** : À mettre en place
|
||||
- **Documentation** : Déjà complète
|
||||
|
||||
### **Budget Marketing**
|
||||
- **Contenu vidéo** : 1000-2000€
|
||||
- **Design infographie** : 500-1000€
|
||||
- **Promotion réseaux sociaux** : 500€
|
||||
- **Événements/conférences** : 2000-5000€
|
||||
|
||||
## 🚨 Gestion des Risques
|
||||
|
||||
### **Risques Identifiés**
|
||||
|
||||
#### 1. **Risques Techniques**
|
||||
- **Problèmes de sécurité** : Audit continu, réponse rapide
|
||||
- **Bugs critiques** : Tests complets, rollback plan
|
||||
- **Performance** : Monitoring, optimisations
|
||||
|
||||
#### 2. **Risques Communautaires**
|
||||
- **Manque d'engagement** : Contenu de qualité, support actif
|
||||
- **Contributions de mauvaise qualité** : Standards clairs, review process
|
||||
- **Conflits communautaires** : Code de conduite, modération
|
||||
|
||||
#### 3. **Risques Business**
|
||||
- **Concurrence** : Innovation continue, différenciation
|
||||
- **Changements réglementaires** : Veille, adaptation
|
||||
- **Évolution technologique** : Roadmap flexible, veille
|
||||
|
||||
### **Plans de Contingence**
|
||||
```bash
|
||||
# Plans de secours
|
||||
- [ ] Plan de rollback technique
|
||||
- [ ] Équipe de support de backup
|
||||
- [ ] Communication de crise
|
||||
- [ ] Ressources alternatives
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Ce plan garantit un lancement open source réussi et une évolution durable du projet docv.** 🚀
|
||||
|
||||
|
||||
343
docs/ROADMAP.md
343
docs/ROADMAP.md
@ -1,343 +0,0 @@
|
||||
# Roadmap de Développement - docv
|
||||
|
||||
## 🗺️ Vue d'Ensemble
|
||||
|
||||
Ce document présente la roadmap de développement du projet docv, détaillant les fonctionnalités planifiées, les améliorations et les évolutions futures.
|
||||
|
||||
### **Vision**
|
||||
docv vise à devenir la référence en matière d'infrastructure open source pour les paiements silencieux Bitcoin, offrant une solution complète, sécurisée et facile à déployer.
|
||||
|
||||
### **Objectifs**
|
||||
- Simplifier le déploiement des paiements silencieux Bitcoin
|
||||
- Créer un écosystème robuste et extensible
|
||||
- Favoriser l'adoption des paiements privés
|
||||
- Construire une communauté active de contributeurs
|
||||
|
||||
## 📅 Timeline de Développement
|
||||
|
||||
### **Phase Actuelle : v1.0.0 (Décembre 2024)**
|
||||
|
||||
#### ✅ **Complété**
|
||||
- Infrastructure Docker complète
|
||||
- Support Bitcoin Core signet
|
||||
- Service Blindbit intégré
|
||||
- SDK Relay avec synchronisation mesh
|
||||
- Documentation technique exhaustive
|
||||
- Tests automatisés
|
||||
- Préparation open source
|
||||
|
||||
#### 🔄 **En Cours**
|
||||
- Lancement open source
|
||||
- Support communautaire
|
||||
- Optimisations de performance
|
||||
|
||||
### **Phase 1 : v1.1.0 (Janvier-Mars 2025)**
|
||||
|
||||
#### 🎯 **Objectifs**
|
||||
- Amélioration de la stabilité
|
||||
- Optimisations de performance
|
||||
- Support communautaire
|
||||
- Documentation enrichie
|
||||
|
||||
#### 📋 **Fonctionnalités Planifiées**
|
||||
|
||||
##### **Stabilité et Performance**
|
||||
- [ ] **Optimisation mémoire** - Réduction de l'empreinte mémoire
|
||||
- [ ] **Amélioration des logs** - Logs structurés et rotation
|
||||
- [ ] **Monitoring avancé** - Métriques détaillées
|
||||
- [ ] **Gestion d'erreurs** - Récupération automatique
|
||||
- [ ] **Tests de charge** - Validation des performances
|
||||
|
||||
##### **Interface Utilisateur**
|
||||
- [ ] **Interface web basique** - Dashboard de monitoring
|
||||
- [ ] **API REST complète** - Endpoints pour la gestion
|
||||
- [ ] **CLI améliorée** - Commandes de gestion
|
||||
- [ ] **Documentation interactive** - Guides interactifs
|
||||
|
||||
##### **Sécurité**
|
||||
- [ ] **Audit de sécurité** - Audit externe complet
|
||||
- [ ] **Chiffrement des données** - Chiffrement des cookies
|
||||
- [ ] **Authentification** - Système d'authentification
|
||||
- [ ] **Certificats SSL/TLS** - Support HTTPS complet
|
||||
|
||||
### **Phase 2 : v1.2.0 (Avril-Juin 2025)**
|
||||
|
||||
#### 🎯 **Objectifs**
|
||||
- Support de nouveaux réseaux Bitcoin
|
||||
- Intégrations tierces
|
||||
- Écosystème d'extensions
|
||||
- Performance avancée
|
||||
|
||||
#### 📋 **Fonctionnalités Planifiées**
|
||||
|
||||
##### **Réseaux Bitcoin**
|
||||
- [ ] **Support mainnet** - Déploiement production
|
||||
- [ ] **Support testnet** - Environnement de test
|
||||
- [ ] **Support regtest** - Tests locaux
|
||||
- [ ] **Multi-réseaux** - Support simultané
|
||||
|
||||
##### **Intégrations**
|
||||
- [ ] **Wallets populaires** - Intégration wallets
|
||||
- [ ] **Exchanges** - Support exchanges
|
||||
- [ ] **Services tiers** - APIs externes
|
||||
- [ ] **Plugins** - Système de plugins
|
||||
|
||||
##### **Performance**
|
||||
- [ ] **Cache distribué** - Cache Redis/Memcached
|
||||
- [ ] **Base de données** - PostgreSQL/MySQL
|
||||
- [ ] **Load balancing** - Équilibrage de charge
|
||||
- [ ] **Auto-scaling** - Mise à l'échelle automatique
|
||||
|
||||
### **Phase 3 : v2.0.0 (Juillet-Décembre 2025)**
|
||||
|
||||
#### 🎯 **Objectifs**
|
||||
- Support Lightning Network
|
||||
- Écosystème complet
|
||||
- Marketplace d'extensions
|
||||
- IA et automatisation
|
||||
|
||||
#### 📋 **Fonctionnalités Planifiées**
|
||||
|
||||
##### **Lightning Network**
|
||||
- [ ] **Nœud Lightning** - LND/c-lightning
|
||||
- [ ] **Paiements Lightning** - Support LN
|
||||
- [ ] **Canaux automatiques** - Gestion des canaux
|
||||
- [ ] **Routage** - Routage Lightning
|
||||
|
||||
##### **Écosystème**
|
||||
- [ ] **Marketplace** - Extensions et plugins
|
||||
- [ ] **SDK complet** - SDK pour développeurs
|
||||
- [ ] **Templates** - Templates de déploiement
|
||||
- [ ] **Intégrations** - Écosystème riche
|
||||
|
||||
##### **Intelligence Artificielle**
|
||||
- [ ] **Monitoring IA** - Détection d'anomalies
|
||||
- [ ] **Optimisation automatique** - Auto-optimisation
|
||||
- [ ] **Prédictions** - Prédictions de charge
|
||||
- [ ] **Chatbot** - Support IA
|
||||
|
||||
### **Phase 4 : v2.1.0 (Janvier-Juin 2026)**
|
||||
|
||||
#### 🎯 **Objectifs**
|
||||
- Support multi-blockchains
|
||||
- Cloud native
|
||||
- Écosystème développeur
|
||||
- Adoption massive
|
||||
|
||||
#### 📋 **Fonctionnalités Planifiées**
|
||||
|
||||
##### **Multi-Blockchains**
|
||||
- [ ] **Ethereum** - Support Ethereum
|
||||
- [ ] **Polkadot** - Support Polkadot
|
||||
- [ ] **Cosmos** - Support Cosmos
|
||||
- [ ] **Interopérabilité** - Cross-chain
|
||||
|
||||
##### **Cloud Native**
|
||||
- [ ] **Kubernetes** - Support K8s
|
||||
- [ ] **Serverless** - Fonctions serverless
|
||||
- [ ] **Microservices** - Architecture microservices
|
||||
- [ ] **Edge computing** - Computing edge
|
||||
|
||||
##### **Écosystème Développeur**
|
||||
- [ ] **API Gateway** - Gateway API
|
||||
- [ ] **Documentation API** - Swagger/OpenAPI
|
||||
- [ ] **SDKs multiples** - SDKs pour différents langages
|
||||
- [ ] **Outils de développement** - IDE plugins
|
||||
|
||||
## 🎯 Fonctionnalités Détaillées
|
||||
|
||||
### **Interface Utilisateur Web**
|
||||
|
||||
#### **Dashboard Principal**
|
||||
```yaml
|
||||
Fonctionnalités:
|
||||
- Vue d'ensemble des services
|
||||
- Métriques en temps réel
|
||||
- Gestion des relais
|
||||
- Configuration avancée
|
||||
- Logs et monitoring
|
||||
- Support et documentation
|
||||
```
|
||||
|
||||
#### **API REST**
|
||||
```yaml
|
||||
Endpoints:
|
||||
- GET /api/v1/status - Statut des services
|
||||
- GET /api/v1/metrics - Métriques système
|
||||
- POST /api/v1/relays - Gestion des relais
|
||||
- PUT /api/v1/config - Configuration
|
||||
- GET /api/v1/logs - Logs système
|
||||
```
|
||||
|
||||
### **Support Lightning Network**
|
||||
|
||||
#### **Architecture LN**
|
||||
```yaml
|
||||
Composants:
|
||||
- LND Node: Nœud Lightning principal
|
||||
- Channel Manager: Gestion des canaux
|
||||
- Payment Router: Routage des paiements
|
||||
- Invoice Manager: Gestion des factures
|
||||
- Network Monitor: Surveillance réseau
|
||||
```
|
||||
|
||||
#### **Intégration**
|
||||
```yaml
|
||||
Fonctionnalités:
|
||||
- Paiements Lightning automatiques
|
||||
- Gestion des canaux
|
||||
- Routage intelligent
|
||||
- Facturation automatique
|
||||
- Monitoring des canaux
|
||||
```
|
||||
|
||||
### **Marketplace d'Extensions**
|
||||
|
||||
#### **Types d'Extensions**
|
||||
```yaml
|
||||
Extensions:
|
||||
- Wallets: Intégrations wallets
|
||||
- Exchanges: Support exchanges
|
||||
- Analytics: Outils d'analyse
|
||||
- Security: Outils de sécurité
|
||||
- Monitoring: Outils de monitoring
|
||||
- Custom: Extensions personnalisées
|
||||
```
|
||||
|
||||
#### **Système de Plugins**
|
||||
```yaml
|
||||
Architecture:
|
||||
- Plugin Manager: Gestionnaire de plugins
|
||||
- API Plugin: API pour plugins
|
||||
- Sandbox: Environnement sécurisé
|
||||
- Registry: Registre de plugins
|
||||
- Updates: Mises à jour automatiques
|
||||
```
|
||||
|
||||
## 📊 Métriques de Succès
|
||||
|
||||
### **Métriques Techniques**
|
||||
|
||||
#### **Performance**
|
||||
- **Temps de réponse** : < 100ms pour les APIs
|
||||
- **Disponibilité** : 99.9% uptime
|
||||
- **Throughput** : 1000+ transactions/seconde
|
||||
- **Latence** : < 50ms pour les paiements
|
||||
|
||||
#### **Qualité**
|
||||
- **Couverture de tests** : > 90%
|
||||
- **Bugs critiques** : 0 en production
|
||||
- **Temps de résolution** : < 24h pour les bugs critiques
|
||||
- **Documentation** : 100% des APIs documentées
|
||||
|
||||
### **Métriques Communautaires**
|
||||
|
||||
#### **Adoption**
|
||||
- **Utilisateurs actifs** : 1000+ utilisateurs
|
||||
- **Contributeurs** : 50+ contributeurs
|
||||
- **Forks** : 100+ forks
|
||||
- **Stars** : 500+ stars
|
||||
|
||||
#### **Engagement**
|
||||
- **Issues résolues** : 90% en < 7 jours
|
||||
- **PR merged** : 80% en < 3 jours
|
||||
- **Discussions actives** : 100+ par mois
|
||||
- **Documentation mise à jour** : Mise à jour continue
|
||||
|
||||
## 🚨 Gestion des Risques
|
||||
|
||||
### **Risques Techniques**
|
||||
|
||||
#### **Performance**
|
||||
- **Risque** : Charge élevée non supportée
|
||||
- **Mitigation** : Tests de charge, auto-scaling
|
||||
- **Plan de contingence** : Architecture distribuée
|
||||
|
||||
#### **Sécurité**
|
||||
- **Risque** : Vulnérabilités de sécurité
|
||||
- **Mitigation** : Audits réguliers, bug bounty
|
||||
- **Plan de contingence** : Response team, patches rapides
|
||||
|
||||
### **Risques Communautaires**
|
||||
|
||||
#### **Adoption**
|
||||
- **Risque** : Faible adoption
|
||||
- **Mitigation** : Marketing actif, documentation claire
|
||||
- **Plan de contingence** : Pivot vers niches spécifiques
|
||||
|
||||
#### **Maintenance**
|
||||
- **Risque** : Manque de mainteneurs
|
||||
- **Mitigation** : Formation, documentation
|
||||
- **Plan de contingence** : Équipe de backup
|
||||
|
||||
## 🎯 Priorités de Développement
|
||||
|
||||
### **Priorité Haute (P0)**
|
||||
1. **Stabilité** - Correction des bugs critiques
|
||||
2. **Sécurité** - Vulnérabilités de sécurité
|
||||
3. **Performance** - Optimisations critiques
|
||||
4. **Documentation** - Documentation essentielle
|
||||
|
||||
### **Priorité Moyenne (P1)**
|
||||
1. **Nouvelles fonctionnalités** - Fonctionnalités majeures
|
||||
2. **Améliorations UX** - Interface utilisateur
|
||||
3. **Intégrations** - Intégrations tierces
|
||||
4. **Monitoring** - Outils de monitoring
|
||||
|
||||
### **Priorité Basse (P2)**
|
||||
1. **Optimisations** - Optimisations mineures
|
||||
2. **Documentation avancée** - Guides avancés
|
||||
3. **Outils de développement** - Outils pour développeurs
|
||||
4. **Expérimentations** - Fonctionnalités expérimentales
|
||||
|
||||
## 📈 Évolution de l'Architecture
|
||||
|
||||
### **Architecture Actuelle (v1.0)**
|
||||
```yaml
|
||||
Services:
|
||||
- Bitcoin Core: Nœud Bitcoin
|
||||
- Blindbit: Service de filtres
|
||||
- SDK Relay: Relais synchronisés
|
||||
- Tor: Proxy anonyme
|
||||
```
|
||||
|
||||
### **Architecture v2.0**
|
||||
```yaml
|
||||
Services:
|
||||
- Bitcoin Core: Nœud Bitcoin
|
||||
- Lightning Node: Nœud Lightning
|
||||
- Blindbit: Service de filtres
|
||||
- SDK Relay: Relais synchronisés
|
||||
- API Gateway: Gateway API
|
||||
- Web UI: Interface web
|
||||
- Monitoring: Monitoring avancé
|
||||
- Tor: Proxy anonyme
|
||||
```
|
||||
|
||||
### **Architecture v3.0**
|
||||
```yaml
|
||||
Services:
|
||||
- Multi-Chain: Support multi-blockchains
|
||||
- Microservices: Architecture microservices
|
||||
- Cloud Native: Support cloud natif
|
||||
- AI/ML: Intelligence artificielle
|
||||
- Marketplace: Marketplace d'extensions
|
||||
- Developer Tools: Outils développeur
|
||||
```
|
||||
|
||||
## 🌟 Vision Long Terme
|
||||
|
||||
### **Objectif 2026**
|
||||
docv devient la plateforme de référence pour les paiements privés et sécurisés, supportant toutes les blockchains majeures et offrant un écosystème complet pour les développeurs et utilisateurs.
|
||||
|
||||
### **Objectif 2027**
|
||||
docv est adopté par des milliers d'utilisateurs et entreprises, contribuant significativement à l'adoption des paiements privés et à l'évolution de l'écosystème blockchain.
|
||||
|
||||
### **Objectif 2028**
|
||||
docv est un standard de l'industrie, avec une communauté mondiale de contributeurs et une influence majeure sur l'évolution des technologies de paiement privé.
|
||||
|
||||
---
|
||||
|
||||
**Cette roadmap guide le développement de docv vers son objectif de devenir la référence en matière d'infrastructure pour les paiements silencieux Bitcoin.** 🚀
|
||||
|
||||
|
||||
@ -1,203 +0,0 @@
|
||||
# Audit de Sécurité - docv
|
||||
|
||||
- CI: job `security-audit` exécutant `scripts/security/audit.sh`.
|
||||
- Portée: npm audit (niveau moderate+), cargo audit si sous-projet Rust, scan de secrets.
|
||||
- Critères bloquants: vulnérabilités élevées/critiques, secrets détectés.
|
||||
- `release-guard` bloque la publication en cas d’échec.
|
||||
|
||||
## 🔍 Résumé de l'Audit
|
||||
|
||||
**Date d'audit** : 19 décembre 2024
|
||||
**Auditeur** : Assistant IA
|
||||
**Version du projet** : 1.0.0
|
||||
**Score de sécurité** : 85/100 ✅
|
||||
|
||||
## 📋 Éléments Audités
|
||||
|
||||
### ✅ **Points Sécurisés**
|
||||
|
||||
#### 1. **Fichiers de Configuration**
|
||||
- ✅ **Cookies Bitcoin** : Utilisation de chemins sécurisés (`/home/bitcoin/.bitcoin/signet/.cookie`)
|
||||
- ✅ **Permissions** : Cookies avec permissions 600 (lecture/écriture propriétaire uniquement)
|
||||
- ✅ **Variables d'environnement** : Pas de secrets en dur dans le code
|
||||
- ✅ **Configuration externalisée** : Fichiers .conf séparés du code
|
||||
|
||||
#### 2. **Infrastructure Docker**
|
||||
- ✅ **Réseau isolé** : Communication via réseau privé `btcnet`
|
||||
- ✅ **Volumes sécurisés** : Données sensibles dans des volumes Docker
|
||||
- ✅ **Healthchecks** : Surveillance de l'état des services
|
||||
- ✅ **Logs** : Rotation et limitation de taille des logs
|
||||
|
||||
#### 3. **Code et Dépendances**
|
||||
- ✅ **Pas de secrets en dur** : Aucun mot de passe ou clé privée dans le code
|
||||
- ✅ **Dépendances Rust** : Utilisation de crates sécurisées
|
||||
- ✅ **Validation des entrées** : Validation des configurations et paramètres
|
||||
- ✅ **Gestion d'erreurs** : Gestion appropriée des erreurs
|
||||
|
||||
### ⚠️ **Points d'Attention**
|
||||
|
||||
#### 1. **URLs et Endpoints**
|
||||
- ⚠️ **dev3.4nkweb.com** : URL externe référencée dans la configuration
|
||||
- ⚠️ **git.4nkweb.com** : URLs du repository Gitea
|
||||
- ✅ **Pas d'endpoints privés** : Toutes les URLs sont publiques et appropriées
|
||||
|
||||
#### 2. **Certificats SSL/TLS**
|
||||
- ⚠️ **Exemples de certificats** : Documentation contient des exemples de génération
|
||||
- ✅ **Pas de certificats réels** : Aucun certificat privé dans le code
|
||||
|
||||
#### 3. **Tests de Connectivité**
|
||||
- ⚠️ **WebSocket tests** : Tests utilisent des clés de test (`Sec-WebSocket-Key: test`)
|
||||
- ✅ **Clés de test uniquement** : Pas de clés de production
|
||||
|
||||
## 🔒 Analyse Détaillée
|
||||
|
||||
### **Fichiers Sensibles**
|
||||
|
||||
#### Cookies Bitcoin Core
|
||||
```bash
|
||||
# Sécurisé ✅
|
||||
/home/bitcoin/.bitcoin/signet/.cookie # Permissions 600
|
||||
/home/bitcoin/.4nk/bitcoin.cookie # Copie sécurisée
|
||||
```
|
||||
|
||||
#### Configuration Files
|
||||
```bash
|
||||
# Sécurisé ✅
|
||||
sdk_relay/.conf # Configuration de base
|
||||
sdk_relay/.conf.docker # Configuration Docker
|
||||
sdk_relay/external_nodes.conf # Nœuds externes
|
||||
```
|
||||
|
||||
#### Docker Volumes
|
||||
```bash
|
||||
# Sécurisé ✅
|
||||
bitcoin_data:/home/bitcoin/.bitcoin # Données Bitcoin
|
||||
blindbit_data:/data # Données Blindbit
|
||||
sdk_relay_*_data:/home/bitcoin/.4nk # Données SDK Relay
|
||||
```
|
||||
|
||||
### **URLs et Endpoints**
|
||||
|
||||
#### URLs Publiques (Approuvées)
|
||||
```bash
|
||||
# Repository Gitea ✅
|
||||
https://git.4nkweb.com/4nk/4NK_node
|
||||
https://git.4nkweb.com/4nk/sdk_relay
|
||||
https://git.4nkweb.com/4nk/sdk_common
|
||||
|
||||
# Nœud externe ✅
|
||||
dev3.4nkweb.com:443 # Relais externe documenté
|
||||
```
|
||||
|
||||
#### URLs de Support (Approuvées)
|
||||
```bash
|
||||
# Support et communication ✅
|
||||
security@4nkweb.com # Signalement de vulnérabilités
|
||||
support@4nkweb.com # Support utilisateur
|
||||
https://forum.4nkweb.com # Forum communautaire
|
||||
```
|
||||
|
||||
### **Variables d'Environnement**
|
||||
|
||||
#### Variables Sécurisées
|
||||
```bash
|
||||
# Configuration Bitcoin ✅
|
||||
BITCOIN_COOKIE_PATH=/home/bitcoin/.bitcoin/signet/.cookie
|
||||
BITCOIN_NETWORK=signet
|
||||
|
||||
# Configuration SDK Relay ✅
|
||||
RUST_LOG=debug
|
||||
ENABLE_SYNC_TEST=1
|
||||
HOME=/home/bitcoin
|
||||
```
|
||||
|
||||
## 🛡️ Recommandations de Sécurité
|
||||
|
||||
### **Actions Immédiates**
|
||||
|
||||
#### 1. **Permissions des Fichiers**
|
||||
```bash
|
||||
# Vérifier les permissions des fichiers sensibles
|
||||
find . -name "*.conf" -exec chmod 600 {} \;
|
||||
find . -name "*.cookie" -exec chmod 600 {} \;
|
||||
```
|
||||
|
||||
#### 2. **Variables d'Environnement**
|
||||
```bash
|
||||
# Utiliser des variables d'environnement pour les secrets
|
||||
export BITCOIN_RPC_PASSWORD="your_secure_password"
|
||||
export BLINDBIT_API_KEY="your_api_key"
|
||||
```
|
||||
|
||||
#### 3. **Monitoring de Sécurité**
|
||||
```bash
|
||||
# Ajouter des tests de sécurité automatisés
|
||||
./tests/run_security_tests.sh
|
||||
```
|
||||
|
||||
### **Actions Recommandées**
|
||||
|
||||
#### 1. **Chiffrement des Données**
|
||||
- Chiffrer les cookies Bitcoin Core
|
||||
- Utiliser des certificats SSL/TLS pour les communications
|
||||
- Implémenter le chiffrement des données sensibles
|
||||
|
||||
#### 2. **Authentification Renforcée**
|
||||
- Implémenter l'authentification multi-facteurs
|
||||
- Utiliser des tokens JWT pour les APIs
|
||||
- Ajouter la validation des certificats clients
|
||||
|
||||
#### 3. **Audit Continu**
|
||||
- Mettre en place un audit de sécurité automatisé
|
||||
- Surveiller les vulnérabilités des dépendances
|
||||
- Tester régulièrement la sécurité
|
||||
|
||||
## 📊 Score de Sécurité
|
||||
|
||||
### **Critères d'Évaluation**
|
||||
|
||||
| Critère | Score | Commentaire |
|
||||
|---------|-------|-------------|
|
||||
| **Secrets en dur** | 100/100 | ✅ Aucun secret trouvé |
|
||||
| **Permissions** | 90/100 | ✅ Permissions appropriées |
|
||||
| **Configuration** | 85/100 | ✅ Configuration externalisée |
|
||||
| **Réseau** | 90/100 | ✅ Isolation Docker |
|
||||
| **Dépendances** | 80/100 | ✅ Dépendances sécurisées |
|
||||
| **Documentation** | 85/100 | ✅ Bonnes pratiques documentées |
|
||||
|
||||
### **Score Global : 85/100** ✅
|
||||
|
||||
## 🚨 Plan d'Action
|
||||
|
||||
### **Phase 1 : Immédiat (1-2 jours)**
|
||||
- [x] Audit de sécurité complet
|
||||
- [x] Vérification des permissions
|
||||
- [x] Nettoyage des fichiers GitHub
|
||||
- [ ] Tests de sécurité automatisés
|
||||
|
||||
### **Phase 2 : Court terme (1 semaine)**
|
||||
- [ ] Implémentation du chiffrement des cookies
|
||||
- [ ] Ajout de certificats SSL/TLS
|
||||
- [ ] Monitoring de sécurité
|
||||
|
||||
### **Phase 3 : Moyen terme (1 mois)**
|
||||
- [ ] Authentification renforcée
|
||||
- [ ] Audit de sécurité automatisé
|
||||
- [ ] Formation sécurité équipe
|
||||
|
||||
## 📚 Ressources
|
||||
|
||||
### **Documentation Sécurité**
|
||||
- [Guide de Sécurité Bitcoin](https://bitcoin.org/en/security)
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [Docker Security Best Practices](https://docs.docker.com/engine/security/)
|
||||
|
||||
### **Outils Recommandés**
|
||||
- **cargo audit** - Audit des dépendances Rust
|
||||
- **Docker Bench Security** - Audit de sécurité Docker
|
||||
- **Bandit** - Analyse de sécurité Python
|
||||
- **SonarQube** - Qualité et sécurité du code
|
||||
|
||||
---
|
||||
|
||||
**Le projet docv présente un bon niveau de sécurité pour l'open source. Les recommandations ci-dessus permettront de renforcer encore la sécurité.** 🔒
|
||||
500
docs/TESTING.md
500
docs/TESTING.md
@ -1,500 +0,0 @@
|
||||
# Guide de Tests - docv
|
||||
|
||||
Ce guide documente l'ensemble des tests disponibles pour l'infrastructure docv, leur organisation et leur utilisation.
|
||||
|
||||
## Vue d'Ensemble
|
||||
|
||||
L'infrastructure docv dispose d'une suite de tests complète organisée en plusieurs catégories :
|
||||
|
||||
- **Tests Unitaires** : Tests individuels des composants
|
||||
- **Tests d'Intégration** : Tests d'interaction entre services
|
||||
- **Tests de Connectivité** : Tests réseau et WebSocket
|
||||
- **Tests Externes** : Tests avec des nœuds externes
|
||||
- **Tests de Performance** : Tests de charge et performance (à venir)
|
||||
|
||||
## Structure des Tests
|
||||
|
||||
```
|
||||
tests/
|
||||
├── README.md # Documentation principale des tests
|
||||
├── run_all_tests.sh # Exécution de tous les tests
|
||||
├── run_unit_tests.sh # Tests unitaires uniquement
|
||||
├── run_integration_tests.sh # Tests d'intégration uniquement
|
||||
├── run_connectivity_tests.sh # Tests de connectivité uniquement
|
||||
├── run_external_tests.sh # Tests externes uniquement
|
||||
├── cleanup.sh # Nettoyage des logs et rapports
|
||||
├── logs/ # Logs des tests
|
||||
├── reports/ # Rapports de tests
|
||||
├── unit/ # Tests unitaires
|
||||
│ ├── test_healthcheck.sh
|
||||
│ ├── test_docker.sh
|
||||
│ ├── test_simple.sh
|
||||
│ └── test_final.sh
|
||||
├── integration/ # Tests d'intégration
|
||||
│ ├── test_3_relays.sh
|
||||
│ ├── test_final_sync.sh
|
||||
│ ├── test_sync_logs.sh
|
||||
│ └── test_messages.sh
|
||||
├── connectivity/ # Tests de connectivité
|
||||
│ ├── test_connectivity.sh
|
||||
│ └── test_websocket_messages.py
|
||||
├── external/ # Tests externes
|
||||
│ ├── test_dev3_simple.py
|
||||
│ ├── test_dev3_connectivity.py
|
||||
│ └── test_integration_dev3.sh
|
||||
└── performance/ # Tests de performance (à créer)
|
||||
```
|
||||
|
||||
## Exécution des Tests
|
||||
|
||||
### Test Complet
|
||||
|
||||
Pour exécuter tous les tests :
|
||||
|
||||
```bash
|
||||
cd tests/
|
||||
./run_all_tests.sh
|
||||
```
|
||||
|
||||
Options disponibles :
|
||||
- `--verbose` : Mode verbose avec affichage détaillé
|
||||
- `--debug` : Mode debug complet
|
||||
- `--skip-unit` : Ignorer les tests unitaires
|
||||
- `--skip-integration` : Ignorer les tests d'intégration
|
||||
- `--skip-connectivity` : Ignorer les tests de connectivité
|
||||
- `--skip-external` : Ignorer les tests externes
|
||||
|
||||
### Tests par Catégorie
|
||||
|
||||
#### Tests Unitaires
|
||||
```bash
|
||||
./tests/run_unit_tests.sh [--verbose] [--debug]
|
||||
```
|
||||
|
||||
**Tests inclus :**
|
||||
- `test_healthcheck.sh` : Test du healthcheck de sdk_relay
|
||||
- `test_docker.sh` : Test de la configuration Docker
|
||||
- `test_simple.sh` : Test simple de sdk_relay
|
||||
- `test_final.sh` : Test final de sdk_relay
|
||||
|
||||
**Prérequis :**
|
||||
- Docker installé et fonctionnel
|
||||
- Image sdk_relay disponible
|
||||
|
||||
#### Tests d'Intégration
|
||||
```bash
|
||||
./tests/run_integration_tests.sh [--verbose] [--debug]
|
||||
```
|
||||
|
||||
**Tests inclus :**
|
||||
- `test_3_relays.sh` : Test de 3 instances sdk_relay
|
||||
- `test_final_sync.sh` : Test complet de synchronisation
|
||||
- `test_sync_logs.sh` : Test des logs de synchronisation
|
||||
- `test_messages.sh` : Test des messages entre relais
|
||||
|
||||
**Prérequis :**
|
||||
- Tous les services Docker démarrés (bitcoin, blindbit, sdk_relay)
|
||||
- Infrastructure complète opérationnelle
|
||||
|
||||
#### Tests de Connectivité
|
||||
```bash
|
||||
./tests/run_connectivity_tests.sh [--verbose] [--debug]
|
||||
```
|
||||
|
||||
**Tests inclus :**
|
||||
- `test_connectivity.sh` : Test de connectivité des services
|
||||
- `test_websocket_messages.py` : Test des messages WebSocket
|
||||
|
||||
**Prérequis :**
|
||||
- Services Docker démarrés
|
||||
- Python3 avec websockets installé
|
||||
|
||||
#### Tests Externes
|
||||
```bash
|
||||
./tests/run_external_tests.sh [--verbose] [--debug]
|
||||
```
|
||||
|
||||
**Tests inclus :**
|
||||
- `test_dev3_simple.py` : Test simple de dev3.4nkweb.com
|
||||
- `test_dev3_connectivity.py` : Test de connectivité dev3
|
||||
- `test_integration_dev3.sh` : Test d'intégration dev3
|
||||
|
||||
**Prérequis :**
|
||||
- Connectivité internet
|
||||
- Python3 avec websockets installé
|
||||
- Services locaux optionnels
|
||||
|
||||
### Test Individuel
|
||||
|
||||
Pour exécuter un test spécifique :
|
||||
|
||||
```bash
|
||||
# Test shell
|
||||
./tests/integration/test_3_relays.sh
|
||||
|
||||
# Test Python
|
||||
python3 tests/external/test_dev3_simple.py
|
||||
```
|
||||
|
||||
## Interprétation des Résultats
|
||||
|
||||
### Codes de Sortie
|
||||
|
||||
- `0` : Test réussi
|
||||
- `1` : Test échoué
|
||||
- `2` : Test ignoré (prérequis non satisfaits)
|
||||
|
||||
### Logs
|
||||
|
||||
Les logs détaillés sont écrits dans `tests/logs/` avec le format :
|
||||
```
|
||||
YYYY-MM-DD_HH-MM-SS_category_tests.log
|
||||
```
|
||||
|
||||
Exemples :
|
||||
- `2024-12-19_14-30-25_unit_tests.log`
|
||||
- `2024-12-19_14-35-12_integration_tests.log`
|
||||
|
||||
### Rapports
|
||||
|
||||
Les rapports JSON sont générés dans `tests/reports/` avec le format :
|
||||
```
|
||||
test_report_YYYY-MM-DD_HH-MM-SS.json
|
||||
```
|
||||
|
||||
Structure du rapport :
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-12-19_14-30-25",
|
||||
"summary": {
|
||||
"total_tests": 10,
|
||||
"successful_tests": 8,
|
||||
"failed_tests": 2,
|
||||
"success_rate": 80.0
|
||||
},
|
||||
"log_file": "tests/logs/test_run_2024-12-19_14-30-25.log",
|
||||
"options": {
|
||||
"verbose": false,
|
||||
"debug": false,
|
||||
"skip_unit": false,
|
||||
"skip_integration": false,
|
||||
"skip_connectivity": false,
|
||||
"skip_external": false,
|
||||
"skip_performance": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Détail des Tests
|
||||
|
||||
### Tests Unitaires
|
||||
|
||||
#### test_healthcheck.sh
|
||||
- **Objectif** : Vérifier le fonctionnement du healthcheck de sdk_relay
|
||||
- **Méthode** : Test du script healthcheck.sh dans un conteneur
|
||||
- **Critères de succès** : Healthcheck retourne un code de sortie approprié
|
||||
|
||||
#### test_docker.sh
|
||||
- **Objectif** : Vérifier la configuration Docker de sdk_relay
|
||||
- **Méthode** : Test de la construction et du démarrage du conteneur
|
||||
- **Critères de succès** : Conteneur démarre correctement
|
||||
|
||||
#### test_simple.sh
|
||||
- **Objectif** : Test simple de sdk_relay
|
||||
- **Méthode** : Démarrage et test basique de sdk_relay
|
||||
- **Critères de succès** : Service répond aux requêtes de base
|
||||
|
||||
#### test_final.sh
|
||||
- **Objectif** : Test final complet de sdk_relay
|
||||
- **Méthode** : Test complet avec toutes les fonctionnalités
|
||||
- **Critères de succès** : Toutes les fonctionnalités opérationnelles
|
||||
|
||||
### Tests d'Intégration
|
||||
|
||||
#### test_3_relays.sh
|
||||
- **Objectif** : Tester 3 instances sdk_relay en parallèle
|
||||
- **Méthode** : Démarrage de 3 relais et vérification de leur interaction
|
||||
- **Critères de succès** : Les 3 relais communiquent correctement
|
||||
|
||||
#### test_final_sync.sh
|
||||
- **Objectif** : Test complet de la synchronisation
|
||||
- **Méthode** : Test de tous les types de synchronisation
|
||||
- **Critères de succès** : Synchronisation fonctionnelle entre tous les relais
|
||||
|
||||
#### test_sync_logs.sh
|
||||
- **Objectif** : Vérifier les logs de synchronisation
|
||||
- **Méthode** : Analyse des logs de synchronisation
|
||||
- **Critères de succès** : Logs cohérents et sans erreurs
|
||||
|
||||
#### test_messages.sh
|
||||
- **Objectif** : Tester l'échange de messages entre relais
|
||||
- **Méthode** : Envoi et réception de messages de test
|
||||
- **Critères de succès** : Messages correctement transmis
|
||||
|
||||
### Tests de Connectivité
|
||||
|
||||
#### test_connectivity.sh
|
||||
- **Objectif** : Vérifier la connectivité entre services
|
||||
- **Méthode** : Test de connectivité réseau entre conteneurs
|
||||
- **Critères de succès** : Tous les services accessibles
|
||||
|
||||
#### test_websocket_messages.py
|
||||
- **Objectif** : Tester les messages WebSocket
|
||||
- **Méthode** : Connexion WebSocket et échange de messages
|
||||
- **Critères de succès** : Communication WebSocket fonctionnelle
|
||||
|
||||
### Tests Externes
|
||||
|
||||
#### test_dev3_simple.py
|
||||
- **Objectif** : Test simple de dev3.4nkweb.com
|
||||
- **Méthode** : Connexion WebSocket simple
|
||||
- **Critères de succès** : Connexion établie
|
||||
|
||||
#### test_dev3_connectivity.py
|
||||
- **Objectif** : Test complet de connectivité dev3
|
||||
- **Méthode** : Tests de protocole et handshake
|
||||
- **Critères de succès** : Tous les protocoles supportés
|
||||
|
||||
#### test_integration_dev3.sh
|
||||
- **Objectif** : Test d'intégration avec dev3
|
||||
- **Méthode** : Test complet d'intégration
|
||||
- **Critères de succès** : Intégration fonctionnelle
|
||||
|
||||
## Dépannage
|
||||
|
||||
### Problèmes Courants
|
||||
|
||||
#### Services non démarrés
|
||||
**Symptôme** : Erreur "Service non trouvé"
|
||||
**Solution** : Démarrer les services avec `./restart_4nk_node.sh`
|
||||
|
||||
#### Connectivité réseau
|
||||
**Symptôme** : Timeout ou erreur de connexion
|
||||
**Solution** : Vérifier les ports et pare-feu
|
||||
|
||||
#### Certificats SSL
|
||||
**Symptôme** : Erreur SSL dans les tests externes
|
||||
**Solution** : Vérifier les certificats et la configuration SSL
|
||||
|
||||
#### Dépendances Python
|
||||
**Symptôme** : ModuleNotFoundError
|
||||
**Solution** : Installer les dépendances avec `pip install websockets`
|
||||
|
||||
### Debug
|
||||
|
||||
#### Mode Verbose
|
||||
```bash
|
||||
./tests/run_all_tests.sh --verbose
|
||||
```
|
||||
|
||||
#### Mode Debug
|
||||
```bash
|
||||
./tests/run_all_tests.sh --debug
|
||||
```
|
||||
|
||||
#### Test spécifique avec debug
|
||||
```bash
|
||||
./tests/integration/test_3_relays.sh --debug
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Nettoyage Automatique
|
||||
|
||||
#### Nettoyer les logs anciens
|
||||
```bash
|
||||
./tests/cleanup.sh --days 7
|
||||
```
|
||||
|
||||
#### Nettoyer les rapports anciens
|
||||
```bash
|
||||
./tests/cleanup.sh --reports --days 30
|
||||
```
|
||||
|
||||
#### Nettoyage complet
|
||||
```bash
|
||||
./tests/cleanup.sh --all --days 7
|
||||
```
|
||||
|
||||
#### Simulation de nettoyage
|
||||
```bash
|
||||
./tests/cleanup.sh --all --dry-run
|
||||
```
|
||||
|
||||
### Surveillance
|
||||
|
||||
#### Vérifier l'espace disque
|
||||
```bash
|
||||
du -sh tests/logs tests/reports
|
||||
```
|
||||
|
||||
#### Lister les fichiers récents
|
||||
```bash
|
||||
find tests/logs -name "*.log" -mtime -1
|
||||
```
|
||||
|
||||
#### Analyser les échecs
|
||||
```bash
|
||||
grep -r "ERROR\|FAILED" tests/logs/
|
||||
```
|
||||
|
||||
## Ajout de Nouveaux Tests
|
||||
|
||||
### Structure Recommandée
|
||||
|
||||
Pour ajouter un nouveau test :
|
||||
|
||||
1. **Créer le fichier de test** dans le répertoire approprié
|
||||
2. **Ajouter le test** au script d'exécution correspondant
|
||||
3. **Documenter le test** dans ce guide
|
||||
4. **Tester le test** pour s'assurer qu'il fonctionne
|
||||
|
||||
### Template de Test Shell
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Test: Description du test
|
||||
# Auteur: Nom
|
||||
# Date: YYYY-MM-DD
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LOG_FILE="tests/logs/$(date +%Y-%m-%d_%H-%M-%S)_test_name.log"
|
||||
|
||||
# Fonctions
|
||||
log() {
|
||||
echo "[$(date +%Y-%m-%d\ %H:%M:%S)] $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Test principal
|
||||
main() {
|
||||
log "Début du test"
|
||||
|
||||
# Vérifications préliminaires
|
||||
check_prerequisites
|
||||
|
||||
# Exécution du test
|
||||
run_test
|
||||
|
||||
# Vérification des résultats
|
||||
verify_results
|
||||
|
||||
log "Test terminé avec succès"
|
||||
}
|
||||
|
||||
# Exécution
|
||||
main "$@"
|
||||
```
|
||||
|
||||
### Template de Test Python
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test: Description du test
|
||||
Auteur: Nom
|
||||
Date: YYYY-MM-DD
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
# Configuration du logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def test_function():
|
||||
"""Fonction de test principale"""
|
||||
logger.info("Début du test")
|
||||
|
||||
try:
|
||||
# Logique de test
|
||||
result = await run_test()
|
||||
|
||||
# Vérification
|
||||
if result:
|
||||
logger.info("Test réussi")
|
||||
return True
|
||||
else:
|
||||
logger.error("Test échoué")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du test: {e}")
|
||||
return False
|
||||
|
||||
async def main():
|
||||
"""Fonction principale"""
|
||||
success = await test_function()
|
||||
exit(0 if success else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Intégration Continue
|
||||
|
||||
### Automatisation
|
||||
|
||||
Les tests peuvent être intégrés dans un pipeline CI/CD :
|
||||
|
||||
```yaml
|
||||
# Exemple GitHub Actions
|
||||
- name: Run Tests
|
||||
run: |
|
||||
cd tests/
|
||||
./run_all_tests.sh --verbose
|
||||
```
|
||||
|
||||
### Surveillance Continue
|
||||
|
||||
Pour une surveillance continue :
|
||||
|
||||
```bash
|
||||
# Cron job pour tests quotidiens
|
||||
0 2 * * * cd /path/to/4NK_node/tests && ./run_all_tests.sh >> /var/log/4nk_tests.log 2>&1
|
||||
```
|
||||
|
||||
### Garde de release
|
||||
|
||||
Avant toute publication (push/tag), le job CI `release-guard` et le script local `scripts/release/guard.sh` exigent des tests verts. Exécution locale:
|
||||
|
||||
```bash
|
||||
RELEASE_TYPE=ci-verify scripts/release/guard.sh
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
Pour obtenir de l'aide :
|
||||
|
||||
1. **Consulter les logs** : `tests/logs/`
|
||||
2. **Vérifier la documentation** : `tests/README.md`
|
||||
3. **Utiliser le mode debug** : `--debug`
|
||||
4. **Consulter les rapports** : `tests/reports/`
|
||||
|
||||
## Évolution
|
||||
|
||||
### Tests de Performance (À venir)
|
||||
|
||||
- Tests de charge
|
||||
- Tests de latence
|
||||
- Tests de débit
|
||||
- Tests de stress
|
||||
|
||||
### Tests de Sécurité (À venir)
|
||||
|
||||
- Tests de vulnérabilités
|
||||
- Tests de pénétration
|
||||
- Tests de configuration
|
||||
|
||||
### Tests d'Interface (À venir)
|
||||
|
||||
- Tests d'API REST
|
||||
- Tests d'interface WebSocket
|
||||
- Tests de compatibilité
|
||||
|
||||
|
||||
671
docs/USAGE.md
671
docs/USAGE.md
@ -1,671 +0,0 @@
|
||||
# 📖 Guide d'Utilisation - docv
|
||||
|
||||
> Ce document est un modèle générique. Remplacez docv et adaptez les commandes/chemins à votre projet.
|
||||
|
||||
Guide complet pour utiliser l'infrastructure docv au quotidien.
|
||||
|
||||
## 🚀 Démarrage Quotidien
|
||||
|
||||
### 1. Démarrage Rapide
|
||||
|
||||
```bash
|
||||
# Démarrer tous les services
|
||||
./restart_4nk_node.sh
|
||||
|
||||
# Vérifier le statut
|
||||
docker ps
|
||||
```
|
||||
|
||||
### 2. Démarrage Séquentiel
|
||||
|
||||
```bash
|
||||
# Démarrer Tor
|
||||
./restart_4nk_node.sh -t
|
||||
|
||||
# Démarrer Bitcoin Core
|
||||
./restart_4nk_node.sh -b
|
||||
|
||||
# Attendre la synchronisation Bitcoin
|
||||
echo "Attendre la synchronisation Bitcoin (10-30 minutes)..."
|
||||
docker logs bitcoin-signet | grep "progress"
|
||||
|
||||
# Démarrer Blindbit
|
||||
./restart_4nk_node.sh -l
|
||||
|
||||
# Démarrer les relais
|
||||
./restart_4nk_node.sh -r
|
||||
```
|
||||
|
||||
### 3. Vérification du Démarrage
|
||||
|
||||
```bash
|
||||
# Vérifier tous les services
|
||||
docker ps
|
||||
|
||||
# Vérifier les logs
|
||||
docker-compose logs --tail=50
|
||||
|
||||
# Vérifier la connectivité
|
||||
./test_final_sync.sh
|
||||
```
|
||||
|
||||
## 🔧 Opérations Quotidiennes
|
||||
|
||||
### 1. Surveillance des Services
|
||||
|
||||
```bash
|
||||
# Statut des services
|
||||
docker ps
|
||||
|
||||
# Logs en temps réel
|
||||
docker-compose logs -f
|
||||
|
||||
# Utilisation des ressources
|
||||
docker stats
|
||||
|
||||
# Espace disque
|
||||
docker system df
|
||||
```
|
||||
|
||||
### 2. Monitoring de la Synchronisation
|
||||
|
||||
```bash
|
||||
# Surveillance de la synchronisation
|
||||
./monitor_sync.sh
|
||||
|
||||
# Test de synchronisation
|
||||
./test_sync_logs.sh
|
||||
|
||||
# Test des messages WebSocket
|
||||
python3 test_websocket_messages.py
|
||||
```
|
||||
|
||||
### 3. Gestion des Logs
|
||||
|
||||
```bash
|
||||
# Logs de tous les services
|
||||
docker-compose logs -f
|
||||
|
||||
# Logs d'un service spécifique
|
||||
docker logs bitcoin-signet
|
||||
docker logs blindbit-oracle
|
||||
docker logs sdk_relay_1
|
||||
|
||||
# Logs avec timestamps
|
||||
docker-compose logs -t
|
||||
|
||||
# Logs depuis une date
|
||||
docker-compose logs --since="2024-01-01T00:00:00"
|
||||
|
||||
# Logs des 100 dernières lignes
|
||||
docker-compose logs --tail=100
|
||||
```
|
||||
|
||||
## 🌐 Utilisation du Réseau de Relais
|
||||
|
||||
### 1. Configuration des Relais
|
||||
|
||||
L'infrastructure utilise 3 relais locaux :
|
||||
|
||||
| Relay | Port WebSocket | Port HTTP | Configuration |
|
||||
|-------|----------------|-----------|---------------|
|
||||
| **Relay 1** | 8090 | 8091 | `sdk_relay/.conf.docker.relay1` |
|
||||
| **Relay 2** | 8092 | 8093 | `sdk_relay/.conf.docker.relay2` |
|
||||
| **Relay 3** | 8094 | 8095 | `sdk_relay/.conf.docker.relay3` |
|
||||
|
||||
### 2. Test de Connectivité des Relais
|
||||
|
||||
```bash
|
||||
# Test de connectivité de base
|
||||
./test_final_sync.sh
|
||||
|
||||
# Test de synchronisation
|
||||
./test_sync_logs.sh
|
||||
|
||||
# Test des messages WebSocket
|
||||
python3 test_websocket_messages.py
|
||||
|
||||
# Test de charge
|
||||
python3 test_websocket_messages.py --load-test
|
||||
```
|
||||
|
||||
### 3. Surveillance de la Synchronisation
|
||||
|
||||
```bash
|
||||
# Surveillance en temps réel
|
||||
./monitor_sync.sh
|
||||
|
||||
# Test de synchronisation forcé
|
||||
./test_sync_logs.sh force
|
||||
|
||||
# Test de synchronisation en continu
|
||||
./test_sync_logs.sh continuous
|
||||
```
|
||||
|
||||
## 🔗 Connexion aux Services
|
||||
|
||||
### 1. Bitcoin Core RPC
|
||||
|
||||
```bash
|
||||
# Connexion via curl
|
||||
curl -u bitcoin:your_password --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockchaininfo", "params": []}' -H 'content-type: text/plain;' http://localhost:18443/
|
||||
|
||||
# Connexion via bitcoin-cli
|
||||
docker exec bitcoin-signet bitcoin-cli -signet getblockchaininfo
|
||||
|
||||
# Vérifier la synchronisation
|
||||
docker exec bitcoin-signet bitcoin-cli -signet getblockchaininfo | jq '.verificationprogress'
|
||||
```
|
||||
|
||||
### 2. Blindbit API
|
||||
|
||||
```bash
|
||||
# Test de connectivité
|
||||
curl -s http://localhost:8000/
|
||||
|
||||
# Vérifier le statut
|
||||
curl -s http://localhost:8000/status
|
||||
|
||||
# Obtenir des filtres
|
||||
curl -s http://localhost:8000/filters
|
||||
```
|
||||
|
||||
### 3. sdk_relay WebSocket
|
||||
|
||||
```bash
|
||||
# Test de connectivité WebSocket
|
||||
curl -v -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: test" http://localhost:8090/
|
||||
|
||||
# Test avec wscat (si installé)
|
||||
wscat -c ws://localhost:8090
|
||||
|
||||
# Test avec Python
|
||||
python3 test_websocket_messages.py
|
||||
```
|
||||
|
||||
## 🧪 Tests et Validation
|
||||
|
||||
### 1. Tests de Base
|
||||
|
||||
```bash
|
||||
# Test de connectivité complet
|
||||
./test_final_sync.sh
|
||||
|
||||
# Test de synchronisation
|
||||
./test_sync_logs.sh
|
||||
|
||||
# Test des messages
|
||||
./test_messages.sh
|
||||
|
||||
# Test des 3 relais
|
||||
./test_3_relays.sh
|
||||
```
|
||||
|
||||
### 2. Tests de Performance
|
||||
|
||||
```bash
|
||||
# Test de charge WebSocket
|
||||
for i in {1..10}; do
|
||||
python3 test_websocket_messages.py &
|
||||
done
|
||||
wait
|
||||
|
||||
# Test de connectivité multiple
|
||||
netstat -tlnp | grep -E "(8090|8092|8094)"
|
||||
|
||||
# Test de performance
|
||||
docker stats --no-stream
|
||||
```
|
||||
|
||||
### 3. Tests de Sécurité
|
||||
|
||||
```bash
|
||||
# Vérifier les ports exposés
|
||||
netstat -tuln | grep -E "(8090|8092|8094)"
|
||||
|
||||
# Vérifier les logs d'accès
|
||||
docker logs sdk_relay_1 | grep -E "(ERROR|WARN)" | tail -20
|
||||
|
||||
# Vérifier l'utilisation des ressources
|
||||
docker stats --no-stream | grep sdk_relay
|
||||
```
|
||||
|
||||
## 🔧 Configuration et Maintenance
|
||||
|
||||
### 1. Modification de Configuration
|
||||
|
||||
```bash
|
||||
# Modifier la configuration Bitcoin Core
|
||||
sudo docker-compose down
|
||||
nano bitcoin/bitcoin.conf
|
||||
sudo docker-compose up -d bitcoin
|
||||
|
||||
# Modifier la configuration Blindbit
|
||||
nano blindbit/blindbit.toml
|
||||
sudo docker-compose restart blindbit
|
||||
|
||||
# Modifier la configuration des relais
|
||||
nano sdk_relay/.conf.docker.relay1
|
||||
sudo docker-compose restart sdk_relay_1
|
||||
```
|
||||
|
||||
### 2. Redémarrage des Services
|
||||
|
||||
```bash
|
||||
# Redémarrage complet
|
||||
./restart_4nk_node.sh
|
||||
|
||||
# Redémarrage d'un service spécifique
|
||||
docker-compose restart bitcoin
|
||||
docker-compose restart blindbit
|
||||
docker-compose restart sdk_relay_1
|
||||
|
||||
# Redémarrage avec reconstruction
|
||||
docker-compose down
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 3. Sauvegarde et Restauration
|
||||
|
||||
```bash
|
||||
# Sauvegarde des données
|
||||
docker exec bitcoin-signet tar czf /tmp/bitcoin-backup.tar.gz /home/bitcoin/.bitcoin
|
||||
docker cp bitcoin-signet:/tmp/bitcoin-backup.tar.gz ./backup/
|
||||
|
||||
# Sauvegarde des configurations
|
||||
tar czf config-backup.tar.gz sdk_relay/.conf* external_nodes.conf
|
||||
|
||||
# Restauration
|
||||
docker cp ./backup/bitcoin-backup.tar.gz bitcoin-signet:/tmp/
|
||||
docker exec bitcoin-signet tar xzf /tmp/bitcoin-backup.tar.gz -C /
|
||||
```
|
||||
|
||||
## 🌐 Gestion des Nœuds Externes
|
||||
|
||||
### 1. Ajout de Nœuds Externes
|
||||
|
||||
```bash
|
||||
# Ajouter un nœud externe
|
||||
./add_external_node.sh add external-relay-1 external-relay-1.example.com:8090
|
||||
|
||||
# Lister les nœuds configurés
|
||||
./add_external_node.sh list
|
||||
|
||||
# Tester la connectivité
|
||||
./add_external_node.sh test external-relay-1
|
||||
|
||||
# Supprimer un nœud
|
||||
./add_external_node.sh remove external-relay-1
|
||||
```
|
||||
|
||||
### 2. Configuration Multi-Sites
|
||||
|
||||
```bash
|
||||
# Site principal
|
||||
./add_external_node.sh add site-paris-1 paris-relay-1.4nk.net:8090
|
||||
./add_external_node.sh add site-paris-2 paris-relay-2.4nk.net:8090
|
||||
|
||||
# Site secondaire
|
||||
./add_external_node.sh add site-lyon-1 lyon-relay-1.4nk.net:8090
|
||||
./add_external_node.sh add site-lyon-2 lyon-relay-2.4nk.net:8090
|
||||
|
||||
# Site de backup
|
||||
./add_external_node.sh add backup-1 backup-relay-1.4nk.net:8090
|
||||
```
|
||||
|
||||
### 3. Test d'Intégration
|
||||
|
||||
```bash
|
||||
# Test d'intégration complet
|
||||
./test_integration_dev3.sh
|
||||
|
||||
# Test de connectivité dev3
|
||||
python3 test_dev3_simple.py
|
||||
|
||||
# Test de connectivité avancé
|
||||
python3 test_dev3_connectivity.py
|
||||
```
|
||||
|
||||
## 📊 Monitoring et Alertes
|
||||
|
||||
### 1. Monitoring de Base
|
||||
|
||||
```bash
|
||||
# Surveillance de la synchronisation
|
||||
./monitor_sync.sh
|
||||
|
||||
# Monitoring en continu
|
||||
while true; do
|
||||
echo "=== $(date) ==="
|
||||
docker stats --no-stream | grep -E "(sdk_relay|bitcoin)"
|
||||
echo "WebSocket connections:"
|
||||
netstat -an | grep :8090 | wc -l
|
||||
sleep 30
|
||||
done
|
||||
```
|
||||
|
||||
### 2. Monitoring Avancé
|
||||
|
||||
```bash
|
||||
# Script de monitoring complet
|
||||
cat > monitor_advanced.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
while true; do
|
||||
clear
|
||||
echo "=== docv Monitoring ==="
|
||||
echo "Date: $(date)"
|
||||
echo ""
|
||||
|
||||
echo "Services:"
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
echo ""
|
||||
|
||||
echo "Ressources:"
|
||||
docker stats --no-stream | grep -E "(sdk_relay|bitcoin|blindbit)"
|
||||
echo ""
|
||||
|
||||
echo "Connexions WebSocket:"
|
||||
netstat -an | grep :8090 | wc -l
|
||||
echo ""
|
||||
|
||||
echo "Espace disque:"
|
||||
df -h | grep -E "(bitcoin|blindbit)"
|
||||
echo ""
|
||||
|
||||
sleep 60
|
||||
done
|
||||
EOF
|
||||
|
||||
chmod +x monitor_advanced.sh
|
||||
./monitor_advanced.sh
|
||||
```
|
||||
|
||||
### 3. Alertes Automatiques
|
||||
|
||||
```bash
|
||||
# Script d'alerte simple
|
||||
cat > alert_monitor.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
# Vérifier Bitcoin Core
|
||||
if ! docker ps | grep -q "bitcoin-signet.*Up"; then
|
||||
echo "ALERTE: Bitcoin Core n'est pas en cours d'exécution!"
|
||||
fi
|
||||
|
||||
# Vérifier les relais
|
||||
for i in {1..3}; do
|
||||
if ! docker ps | grep -q "sdk_relay_$i.*Up"; then
|
||||
echo "ALERTE: Relay $i n'est pas en cours d'exécution!"
|
||||
fi
|
||||
done
|
||||
|
||||
# Vérifier l'espace disque
|
||||
if [ $(df / | awk 'NR==2 {print $5}' | sed 's/%//') -gt 90 ]; then
|
||||
echo "ALERTE: Espace disque faible!"
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x alert_monitor.sh
|
||||
|
||||
# Ajouter au cron pour surveillance automatique
|
||||
echo "*/5 * * * * /path/to/alert_monitor.sh" | crontab -
|
||||
```
|
||||
|
||||
## 🔒 Sécurité
|
||||
|
||||
### 1. Vérification de Sécurité
|
||||
|
||||
```bash
|
||||
# Vérifier les ports exposés
|
||||
netstat -tuln | grep -E "(8090|8092|8094)"
|
||||
|
||||
# Vérifier les permissions
|
||||
ls -la sdk_relay/.conf*
|
||||
ls -la bitcoin/bitcoin.conf
|
||||
ls -la blindbit/blindbit.toml
|
||||
|
||||
# Vérifier les logs de sécurité
|
||||
docker logs sdk_relay_1 | grep -E "(ERROR|WARN|SECURITY)" | tail -20
|
||||
```
|
||||
|
||||
### 2. Configuration de Pare-feu
|
||||
|
||||
```bash
|
||||
# Autoriser seulement les ports nécessaires
|
||||
sudo ufw allow 18443/tcp # Bitcoin Core RPC
|
||||
sudo ufw allow 8090/tcp # sdk_relay WebSocket
|
||||
sudo ufw allow 8000/tcp # Blindbit API
|
||||
sudo ufw enable
|
||||
|
||||
# Vérifier les règles
|
||||
sudo ufw status numbered
|
||||
```
|
||||
|
||||
### 3. Rotation des Logs
|
||||
|
||||
```bash
|
||||
# Configuration de rotation des logs
|
||||
cat > /etc/logrotate.d/4nk-node << EOF
|
||||
/var/lib/docker/containers/*/*.log {
|
||||
daily
|
||||
rotate 7
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
copytruncate
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## 🚨 Dépannage
|
||||
|
||||
### 1. Problèmes Courants
|
||||
|
||||
#### Service Ne Démarre Pas
|
||||
|
||||
```bash
|
||||
# Vérifier les logs
|
||||
docker logs <service_name>
|
||||
|
||||
# Vérifier la configuration
|
||||
docker exec <service_name> cat /path/to/config
|
||||
|
||||
# Redémarrer le service
|
||||
docker restart <service_name>
|
||||
```
|
||||
|
||||
#### Problèmes de Connectivité
|
||||
|
||||
```bash
|
||||
# Tester la connectivité réseau
|
||||
docker exec <service_name> ping <target>
|
||||
|
||||
# Vérifier la résolution DNS
|
||||
docker exec <service_name> nslookup <target>
|
||||
|
||||
# Tester les ports
|
||||
docker exec <service_name> nc -z <target> <port>
|
||||
```
|
||||
|
||||
#### Problèmes de Synchronisation
|
||||
|
||||
```bash
|
||||
# Vérifier les logs de synchronisation
|
||||
docker logs sdk_relay_1 | grep -E "(Sync|Relay|Mesh)"
|
||||
|
||||
# Forcer la synchronisation
|
||||
docker restart sdk_relay_1 sdk_relay_2 sdk_relay_3
|
||||
|
||||
# Vérifier la connectivité entre relais
|
||||
./test_sync_logs.sh force
|
||||
```
|
||||
|
||||
### 2. Logs de Debug
|
||||
|
||||
```bash
|
||||
# Logs détaillés
|
||||
docker-compose logs -f --tail=100
|
||||
|
||||
# Logs d'un service spécifique
|
||||
docker logs <service_name> -f
|
||||
|
||||
# Logs avec timestamps
|
||||
docker-compose logs -t
|
||||
|
||||
# Logs depuis une date
|
||||
docker-compose logs --since="2024-01-01T00:00:00"
|
||||
```
|
||||
|
||||
### 3. Outils de Debug
|
||||
|
||||
```bash
|
||||
# Debug du container sdk_relay
|
||||
./sdk_relay/debug_container.sh
|
||||
|
||||
# Test du healthcheck
|
||||
./sdk_relay/test_healthcheck.sh
|
||||
|
||||
# Test de connectivité
|
||||
./sdk_relay/test_connectivity.sh
|
||||
|
||||
# Test simple
|
||||
./sdk_relay/test_simple.sh
|
||||
```
|
||||
|
||||
## 📈 Performance
|
||||
|
||||
### 1. Optimisation
|
||||
|
||||
```bash
|
||||
# Limiter l'utilisation CPU
|
||||
docker-compose up -d --scale bitcoin=1
|
||||
|
||||
# Optimiser la mémoire
|
||||
docker stats --no-stream | grep sdk_relay
|
||||
|
||||
# Nettoyer l'espace disque
|
||||
docker system prune -f
|
||||
```
|
||||
|
||||
### 2. Monitoring de Performance
|
||||
|
||||
```bash
|
||||
# Surveillance des ressources
|
||||
docker stats
|
||||
|
||||
# Surveillance des connexions
|
||||
netstat -an | grep :8090 | wc -l
|
||||
|
||||
# Surveillance de l'espace disque
|
||||
df -h
|
||||
```
|
||||
|
||||
### 3. Tests de Charge
|
||||
|
||||
```bash
|
||||
# Test de charge simple
|
||||
for i in {1..50}; do
|
||||
python3 test_websocket_messages.py &
|
||||
sleep 0.1
|
||||
done
|
||||
wait
|
||||
|
||||
# Test de charge avancé
|
||||
python3 test_websocket_messages.py --load-test --duration=300
|
||||
```
|
||||
|
||||
## 🔄 Maintenance
|
||||
|
||||
### 1. Sauvegarde Régulière
|
||||
|
||||
```bash
|
||||
# Script de sauvegarde automatique
|
||||
cat > backup_4nk.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_DIR="/backup/4nk_node_$DATE"
|
||||
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# Sauvegarder les configurations
|
||||
cp -r sdk_relay/.conf* $BACKUP_DIR/
|
||||
cp external_nodes.conf $BACKUP_DIR/
|
||||
|
||||
# Sauvegarder les données Bitcoin
|
||||
docker exec bitcoin-signet tar czf /tmp/bitcoin-backup.tar.gz /home/bitcoin/.bitcoin
|
||||
docker cp bitcoin-signet:/tmp/bitcoin-backup.tar.gz $BACKUP_DIR/
|
||||
|
||||
echo "Sauvegarde terminée: $BACKUP_DIR"
|
||||
EOF
|
||||
|
||||
chmod +x backup_4nk.sh
|
||||
```
|
||||
|
||||
### 2. Mise à Jour
|
||||
|
||||
```bash
|
||||
# Mise à jour de l'infrastructure
|
||||
git pull origin main
|
||||
./restart_4nk_node.sh
|
||||
|
||||
# Mise à jour des images
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 3. Nettoyage
|
||||
|
||||
```bash
|
||||
# Nettoyer les conteneurs arrêtés
|
||||
docker container prune -f
|
||||
|
||||
# Nettoyer les images non utilisées
|
||||
docker image prune -f
|
||||
|
||||
# Nettoyer les volumes non utilisés
|
||||
docker volume prune -f
|
||||
|
||||
# Nettoyer tout
|
||||
docker system prune -a -f
|
||||
```
|
||||
|
||||
## 📝 Checklist Quotidienne
|
||||
|
||||
- [ ] Services démarrés et fonctionnels
|
||||
- [ ] Bitcoin Core synchronisé
|
||||
- [ ] Relais connectés et synchronisés
|
||||
- [ ] Tests de connectivité passés
|
||||
- [ ] Logs vérifiés (pas d'erreurs critiques)
|
||||
- [ ] Ressources système OK
|
||||
- [ ] Sauvegarde effectuée (si nécessaire)
|
||||
- [ ] Monitoring actif
|
||||
|
||||
## 🎯 Commandes Rapides
|
||||
|
||||
```bash
|
||||
# Démarrage rapide
|
||||
./restart_4nk_node.sh
|
||||
|
||||
# Statut des services
|
||||
docker ps
|
||||
|
||||
# Logs en temps réel
|
||||
docker-compose logs -f
|
||||
|
||||
# Test de connectivité
|
||||
./test_final_sync.sh
|
||||
|
||||
# Surveillance
|
||||
./monitor_sync.sh
|
||||
|
||||
# Arrêt propre
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**✨ Infrastructure docv - Utilisation optimale !**
|
||||
|
||||
|
||||
39
docs/authentication.md
Normal file
39
docs/authentication.md
Normal file
@ -0,0 +1,39 @@
|
||||
### Authentification 4NK (iframe + MessageBus)
|
||||
|
||||
Cette application intègre un flux d’authentification 4NK encapsulé dans une `iframe` et orchestré côté client via `MessageBus`.
|
||||
|
||||
### Composants impliqués
|
||||
|
||||
- `components/4nk/AuthModal.tsx`: modal d’auth 4NK, gère l’état d’affichage, le chargement, les erreurs et la progression
|
||||
- `components/4nk/Iframe.tsx` et `lib/4nk/IframeReference.ts`: gestion de l’`iframe` 4NK et de sa référence DOM
|
||||
- `lib/4nk/MessageBus.ts`: protocole de communication `window.postMessage` avec l’iframe
|
||||
- `lib/4nk/UserStore.ts`: stockage des tokens (sessionStorage) et pairingId utilisateur
|
||||
- `lib/4nk/MockService.ts`: implémentations simulées pour le **mode démonstration**
|
||||
|
||||
### Protocole côté client
|
||||
|
||||
Séquence attendue côté UI lors d’une authentification standard :
|
||||
|
||||
1. L’`iframe` se charge et envoie un message `LISTENING`
|
||||
2. Le client envoie `REQUEST_LINK`
|
||||
3. Réception `LINK_ACCEPTED` avec `accessToken` et `refreshToken`
|
||||
4. Le client envoie `GET_PAIRING_ID` pour récupérer l’identifiant d’appairage
|
||||
|
||||
Le `MessageBus` fournit des méthodes utilitaires supplémentaires (`validateToken`, `renewToken`, `getProcesses`, etc.) utilisées par l’espace `/dashboard`.
|
||||
|
||||
### Mode démonstration (mock)
|
||||
|
||||
- Sur `/login`, saisir l’identifiant d’entreprise `1234` active le **mode mock** dans `MessageBus`
|
||||
- Les appels sont alors redirigés vers `MockService` (tokens et données factices), sans dépendre de l’iframe
|
||||
- Le dashboard affiche alors des statistiques et listes récentes simulées
|
||||
|
||||
### Configuration
|
||||
|
||||
- `NEXT_PUBLIC_4NK_IFRAME_URL`: origine de l’iframe (ex. `https://dev.4nk.io`)
|
||||
- Contrainte d’origine: `MessageBus` n’accepte que les événements provenant d’origines 4NK/localhost
|
||||
|
||||
### Comportements principaux
|
||||
|
||||
- Stockage des tokens via `UserStore.connect(accessToken, refreshToken)`
|
||||
- Appairage: `getUserPairingId()` stocke l’ID côté `UserStore`
|
||||
- Vérification en dashboard: `validateToken()` côté 4NK (ou mock) et affichage conditionnel
|
||||
42
docs/build-and-run.md
Normal file
42
docs/build-and-run.md
Normal file
@ -0,0 +1,42 @@
|
||||
### Installation, build et exécution
|
||||
|
||||
Ces instructions couvrent l’installation des dépendances, l’exécution en développement et le build de production pour la partie Next.js.
|
||||
|
||||
### Prérequis
|
||||
|
||||
- Node.js 20+ recommandé
|
||||
- Accès réseau pour installer les dépendances NPM
|
||||
|
||||
### Installation des dépendances
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
```
|
||||
|
||||
### Exécution en développement
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Build de production
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Démarrage de la production locale
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
### Variables d’environnement utiles
|
||||
|
||||
- `NEXT_PUBLIC_4NK_IFRAME_URL`
|
||||
- SMTP (`SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_FROM`)
|
||||
|
||||
### Notes
|
||||
|
||||
- `next.config.mjs` ignore les erreurs ESLint/TS au build (utile pour la démo)
|
||||
- La présence d’un dossier `src/` + `vite.config.ts` est résiduelle et non utilisée par `npm run` à ce stade
|
||||
30
docs/dashboard.md
Normal file
30
docs/dashboard.md
Normal file
@ -0,0 +1,30 @@
|
||||
### Dashboard (navigation, état, données mock)
|
||||
|
||||
Le dossier `app/dashboard/` contient le layout et la page principale du tableau de bord. Il s’appuie sur l’auth 4NK et propose un **mode démonstration**.
|
||||
|
||||
### Layout
|
||||
|
||||
- Fichier: `app/dashboard/layout.tsx`
|
||||
- Rôle: structure globale (sidebar, topbar, navigation, déconnexion)
|
||||
- Auth: contrôle l’état authentifié via `UserStore` et `MessageBus`
|
||||
- Mode démo: badge et mentions spécifiques lorsque `MessageBus.isInMockMode()` vaut `true`
|
||||
|
||||
### Page principale
|
||||
|
||||
- Fichier: `app/dashboard/page.tsx`
|
||||
- Rôle: affichage des indicateurs et sections utiles (le chat est en tête de page)
|
||||
- Données: simulées en **mode mock** (statistiques de stockage, dossiers, activités)
|
||||
|
||||
### Navigation
|
||||
|
||||
- Entrées: Tableau de bord, Documents, Dossiers, Recherche, Utilisateurs, Messages, Paramètres
|
||||
- Dépendances: certaines pages peuvent être des squelettes à compléter
|
||||
|
||||
### Points d’attention
|
||||
|
||||
- En mode production, la récupération de données doit passer par `MessageBus` (et non mock)
|
||||
- Les « cartes » Documents/Dossiers/Collaborateurs ont été notées à retirer dans un commentaire pour alléger l’UI
|
||||
- **Chat intégré**: l’interface Messages est intégrée en tête de page (hauteur ~600px)
|
||||
- **Sections retirées**: les blocs « Documents récents » et « Activité récente » ont été supprimés du dashboard
|
||||
- **Ordonnancement**: Chat global « My Work » en premier, puis indicateurs (Stockage permanent, Stockage temporaire, Nouveaux dossiers)
|
||||
- **Menu supprimé**: l’entrée de navigation « Messages » a été retirée de la sidebar; la route `/dashboard/chat` peut subsister sans lien direct
|
||||
59
docs/overview.md
Normal file
59
docs/overview.md
Normal file
@ -0,0 +1,59 @@
|
||||
### Aperçu du dépôt
|
||||
|
||||
Ce projet est une application web basée sur Next.js (App Router) avec React et TypeScript. Elle propose un site public (accueil, formation, contact) et un espace authentifié de type « dashboard » intégrant un flux d’authentification 4NK via iframe, avec un mode démonstration activable.
|
||||
|
||||
### Pile technique
|
||||
|
||||
- **Framework**: `next` 15 (App Router dans `app/`)
|
||||
- **Langage**: `typescript` 5
|
||||
- **UI**: `react` 19, composants `shadcn/ui` (via `components/ui`), `lucide-react`
|
||||
- **Styles**: `tailwindcss` 4, fichiers dans `styles/` et `app/globals.css`
|
||||
- **Formulaires/Validation**: `react-hook-form` et `zod` (validation côté serveur dans les actions)
|
||||
- **Emailing**: `nodemailer` (configuration SMTP via variables d’environnement)
|
||||
- **Outils 4NK**: `lib/4nk` (MessageBus, UserStore, MockService, EventBus)
|
||||
- **Divers**: `postcss`, `autoprefixer`, `date-fns`, `uuid`
|
||||
|
||||
### Structure principale
|
||||
|
||||
- `app/`
|
||||
- `page.tsx`: page d’accueil publique (sections produit, sécurité, tarifs, liens vers formation, login)
|
||||
- `layout.tsx`: layout racine de l’app (police, métadonnées, `globals.css`)
|
||||
- `contact/page.tsx`: formulaire de contact (client) + action serveur `app/actions/contact.ts`
|
||||
- `formation/page.tsx`: page formations (CTA vers devis); `formation/devis` (présent en dossier) si implémenté
|
||||
- `login/page.tsx`: écran de login (auth 4NK via modal + mode démo)
|
||||
- `dashboard/`: layout + pages (mock data en mode démo)
|
||||
- `actions/`: actions serveur pour `contact` et `formation`
|
||||
- `components/`
|
||||
- `ui/`: primitives UI (boutons, cards, inputs, etc.)
|
||||
- `4nk/`: composants spécifiques 4NK (`AuthModal`, `Iframe`, `DebugInfo` si utilisé)
|
||||
- `lib/`
|
||||
- `email.ts`: fonctions d’envoi d’emails (contact, formation)
|
||||
- `4nk/`: bus de messages, stockage utilisateur, services mock
|
||||
- `public/`: assets (logos, placeholders)
|
||||
- `styles/`: styles globaux
|
||||
- `next.config.mjs`: configuration Next (build sans bloquer sur ESLint/TS, images non optimisées)
|
||||
- `tsconfig.json`: configuration TypeScript
|
||||
- `package.json`: scripts Next (`dev`, `build`, `start`, `lint`) et dépendances
|
||||
- `vite.config.ts` et `src/`: présence d’un squelette Vite non référencé par les scripts NPM (probablement résiduel)
|
||||
|
||||
### Fonctionnalités clés
|
||||
|
||||
- **Site public**: présentation du produit DocV, références, offre tarifaire, formations
|
||||
- **Formulaire de contact**: envoi d’un email structuré via `nodemailer` (action serveur `submitContactForm`)
|
||||
- **Demande de devis formation**: envoi d’un email structuré via `nodemailer` (action serveur `submitFormationForm`)
|
||||
- **Authentification 4NK**:
|
||||
- Modal d’authentification intégrant une `iframe` 4NK
|
||||
- Protocole côté client via `MessageBus` (LISTENING → REQUEST_LINK → LINK_ACCEPTED → GET_PAIRING_ID)
|
||||
- **Mode démonstration**: saisir `1234` comme identifiant d’entreprise sur `/login` active le mock local
|
||||
- **Dashboard**: statistiques et listes « récentes » simulées en mode démo, navigation latérale, vérification token en mode non-mock
|
||||
|
||||
### Variables d’environnement importantes
|
||||
|
||||
- SMTP: `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_FROM`
|
||||
- Auth 4NK (UI): `NEXT_PUBLIC_4NK_IFRAME_URL` (ex. `https://dev.4nk.io`)
|
||||
|
||||
### Points d’attention
|
||||
|
||||
- `next.config.mjs` désactive les erreurs ESLint/TS au build (utile pour démo, à challenger pour la prod)
|
||||
- Le dossier `src/` et `vite.config.ts` semblent non utilisés par les scripts; valider leur pertinence
|
||||
- Les envois d’emails exigent des variables d’environnement valides côté serveur (build/exec)
|
||||
32
docs/users-invitations.md
Normal file
32
docs/users-invitations.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Invitations utilisateurs (passphrase)
|
||||
---
|
||||
|
||||
### Principe
|
||||
|
||||
- **Composition**: 4 mots français « existants » + **code** aléatoire de 6 caractères.
|
||||
- **Rôle**: obligatoire (Lecteur, Éditeur, Administrateur).
|
||||
- **Contexte**: titre de document/dossier optionnel.
|
||||
- **Lien**: un lien d’invitation est aussi généré et copiable.
|
||||
- **Email**: champ optionnel pour envoyer l’invitation.
|
||||
|
||||
### Saisie côté invité
|
||||
|
||||
- **Étapes**: saisir les 4 mots dans l’ordre puis le code sur la page de connexion.
|
||||
- **Alternative**: utiliser le **lien** d’invitation.
|
||||
|
||||
### Détails d’implémentation
|
||||
|
||||
- **Génération**: 4 mots uniques issus de la **liste BIP-39 (français)** + code alphanumérique (A..Z, 2..9) de 6 caractères.
|
||||
- **Affichage**: la modale montre la passphrase, un bouton « Copier », le lien et un champ email d’envoi.
|
||||
- **Email**: inclut rôle, contexte (si fourni), passphrase et éventuellement le lien.
|
||||
|
||||
### Sécurité
|
||||
|
||||
- **Standard**: liste standardisée BIP-39 (français), certains mots contiennent des accents.
|
||||
- **Bonnes pratiques**: éviter le partage de la passphrase sur des canaux non sécurisés.
|
||||
|
||||
### Limites connues
|
||||
|
||||
- **Expiration**: pas d’expiration de lien/token par défaut.
|
||||
- **Validation serveur**: non implémentée dans cette version.
|
||||
110
lib/email.ts
110
lib/email.ts
@ -64,7 +64,7 @@ export async function sendContactEmail(data: ContactFormData) {
|
||||
|
||||
// Adresse de destination explicite
|
||||
const destinationEmail = 'contact@docv.fr'
|
||||
|
||||
|
||||
const servicesText = data.services.length > 0 ? data.services.join(', ') : 'Aucun service sélectionné'
|
||||
|
||||
const mailOptions = {
|
||||
@ -77,7 +77,7 @@ export async function sendContactEmail(data: ContactFormData) {
|
||||
<div style="background: #2563eb; color: white; padding: 20px; text-align: center;">
|
||||
<h1>🔐 DocV - Nouvelle demande de contact</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="padding: 20px;">
|
||||
<h2 style="color: #2563eb;">👤 Informations de contact</h2>
|
||||
<p><strong>Nom :</strong> ${data.nom}</p>
|
||||
@ -86,39 +86,39 @@ export async function sendContactEmail(data: ContactFormData) {
|
||||
${data.telephone ? `<p><strong>Téléphone :</strong> ${data.telephone}</p>` : ''}
|
||||
${data.entreprise ? `<p><strong>Entreprise :</strong> ${data.entreprise}</p>` : ''}
|
||||
${data.fonction ? `<p><strong>Fonction :</strong> ${data.fonction}</p>` : ''}
|
||||
|
||||
|
||||
<h2 style="color: #2563eb;">🎯 Projet</h2>
|
||||
${data.typeProjet ? `<p><strong>Type de projet :</strong> ${data.typeProjet}</p>` : ''}
|
||||
${data.budget ? `<p><strong>Budget estimé :</strong> ${data.budget}</p>` : ''}
|
||||
${data.delai ? `<p><strong>Délai souhaité :</strong> ${data.delai}</p>` : ''}
|
||||
|
||||
|
||||
<h2 style="color: #2563eb;">📝 Description</h2>
|
||||
<div style="background: #f9fafb; padding: 15px; border-left: 4px solid #2563eb;">
|
||||
${data.description.replace(/\n/g, '<br>')}
|
||||
</div>
|
||||
|
||||
|
||||
${data.objectifs ? `
|
||||
<h3 style="color: #10b981;">🎯 Objectifs</h3>
|
||||
<div style="background: #f0fdf4; padding: 15px; border-left: 4px solid #10b981;">
|
||||
${data.objectifs.replace(/\n/g, '<br>')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
|
||||
${data.contraintes ? `
|
||||
<h3 style="color: #f59e0b;">⚠️ Contraintes</h3>
|
||||
<div style="background: #fefce8; padding: 15px; border-left: 4px solid #f59e0b;">
|
||||
${data.contraintes.replace(/\n/g, '<br>')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
|
||||
<h2 style="color: #2563eb;">🛠️ Services demandés</h2>
|
||||
<p>${servicesText}</p>
|
||||
|
||||
|
||||
<h2 style="color: #2563eb;">⚙️ Options</h2>
|
||||
<p><strong>Démonstration souhaitée :</strong> ${data.demo ? '✅ Oui' : '❌ Non'}</p>
|
||||
<p><strong>Accompagnement personnalisé :</strong> ${data.accompagnement ? '✅ Oui' : '❌ Non'}</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="background: #f3f4f6; padding: 15px; text-center; font-size: 12px; color: #6b7280;">
|
||||
<p>📧 Message envoyé depuis <strong>docv.fr</strong> le ${new Date().toLocaleString('fr-FR')}</p>
|
||||
<p>🔐 DocV - Solutions de souveraineté numérique by 4NK</p>
|
||||
@ -160,18 +160,18 @@ Message envoyé depuis docv.fr le ${new Date().toLocaleString('fr-FR')}
|
||||
|
||||
console.log('📤 Envoi vers:', destinationEmail)
|
||||
console.log('📤 Depuis:', process.env.SMTP_USER)
|
||||
|
||||
|
||||
const result = await transporter.sendMail(mailOptions)
|
||||
console.log('✅ Email contact envoyé:', result.messageId)
|
||||
|
||||
|
||||
return { success: true }
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Erreur envoi email contact:', error.message)
|
||||
console.error('❌ Stack:', error.stack)
|
||||
return {
|
||||
success: false,
|
||||
error: `Erreur d'envoi: ${error.message}`
|
||||
return {
|
||||
success: false,
|
||||
error: `Erreur d'envoi: ${error.message}`
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,7 +199,7 @@ export async function sendFormationEmail(data: FormationFormData) {
|
||||
|
||||
// Adresse de destination explicite
|
||||
const destinationEmail = 'contact@docv.fr'
|
||||
|
||||
|
||||
const formationsText = data.formations.length > 0 ? data.formations.join(', ') : 'Aucune formation sélectionnée'
|
||||
|
||||
const mailOptions = {
|
||||
@ -212,54 +212,54 @@ export async function sendFormationEmail(data: FormationFormData) {
|
||||
<div style="background: #059669; color: white; padding: 20px; text-align: center;">
|
||||
<h1>🎓 DocV - Demande de devis formation</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="padding: 20px;">
|
||||
<h2 style="color: #059669;">🏢 Informations entreprise</h2>
|
||||
<p><strong>Entreprise :</strong> ${data.entreprise}</p>
|
||||
${data.secteur ? `<p><strong>Secteur :</strong> ${data.secteur}</p>` : ''}
|
||||
${data.taille ? `<p><strong>Taille :</strong> ${data.taille}</p>` : ''}
|
||||
${data.siret ? `<p><strong>SIRET :</strong> ${data.siret}</p>` : ''}
|
||||
|
||||
|
||||
<h2 style="color: #059669;">👤 Contact</h2>
|
||||
<p><strong>Nom :</strong> ${data.nom}</p>
|
||||
<p><strong>Prénom :</strong> ${data.prenom}</p>
|
||||
<p><strong>Email :</strong> <a href="mailto:${data.email}">${data.email}</a></p>
|
||||
${data.telephone ? `<p><strong>Téléphone :</strong> ${data.telephone}</p>` : ''}
|
||||
${data.fonction ? `<p><strong>Fonction :</strong> ${data.fonction}</p>` : ''}
|
||||
|
||||
|
||||
<h2 style="color: #059669;">📚 Formations souhaitées</h2>
|
||||
<div style="background: #ecfdf5; padding: 15px; border-radius: 8px; border-left: 4px solid #059669;">
|
||||
<strong>${formationsText}</strong>
|
||||
</div>
|
||||
|
||||
|
||||
<h2 style="color: #059669;">📅 Modalités</h2>
|
||||
${data.modalite ? `<p><strong>Mode de formation :</strong> ${data.modalite}</p>` : ''}
|
||||
${data.participants ? `<p><strong>Nombre de participants :</strong> ${data.participants}</p>` : ''}
|
||||
${data.dates ? `<p><strong>Période souhaitée :</strong> ${data.dates}</p>` : ''}
|
||||
${data.lieu ? `<p><strong>Lieu :</strong> ${data.lieu}</p>` : ''}
|
||||
|
||||
|
||||
${data.objectifs ? `
|
||||
<h2 style="color: #059669;">🎯 Objectifs</h2>
|
||||
<div style="background: #f0fdf4; padding: 15px; border-left: 4px solid #059669;">
|
||||
${data.objectifs.replace(/\n/g, '<br>')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
|
||||
${data.niveau ? `<p><strong>Niveau des participants :</strong> ${data.niveau}</p>` : ''}
|
||||
|
||||
|
||||
${data.contraintes ? `
|
||||
<h3 style="color: #f59e0b;">⚠️ Contraintes</h3>
|
||||
<div style="background: #fefce8; padding: 15px; border-left: 4px solid #f59e0b;">
|
||||
${data.contraintes.replace(/\n/g, '<br>')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
|
||||
<h2 style="color: #059669;">⚙️ Options</h2>
|
||||
<p><strong>Certification RNCP :</strong> ${data.certification ? '✅ Oui' : '❌ Non'}</p>
|
||||
<p><strong>Support 6 mois :</strong> ${data.support ? '✅ Oui' : '❌ Non'}</p>
|
||||
<p><strong>Accompagnement personnalisé :</strong> ${data.accompagnement ? '✅ Oui' : '❌ Non'}</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="background: #f3f4f6; padding: 15px; text-center; font-size: 12px; color: #6b7280;">
|
||||
<p>📧 Message envoyé depuis <strong>docv.fr</strong> le ${new Date().toLocaleString('fr-FR')}</p>
|
||||
<p>🎓 DocV Formation - Centre agréé 4NK</p>
|
||||
@ -299,18 +299,70 @@ Message envoyé depuis docv.fr le ${new Date().toLocaleString('fr-FR')}
|
||||
|
||||
console.log('📤 Envoi formation vers:', destinationEmail)
|
||||
console.log('📤 Depuis:', process.env.SMTP_USER)
|
||||
|
||||
|
||||
const result = await transporter.sendMail(mailOptions)
|
||||
console.log('✅ Email formation envoyé:', result.messageId)
|
||||
|
||||
|
||||
return { success: true }
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Erreur envoi email formation:', error.message)
|
||||
console.error('❌ Stack:', error.stack)
|
||||
return {
|
||||
success: false,
|
||||
error: `Erreur d'envoi: ${error.message}`
|
||||
return {
|
||||
success: false,
|
||||
error: `Erreur d'envoi: ${error.message}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendUserInviteEmail(params: { recipientEmail: string; role: string; words: string[]; code: string; resourceTitle?: string; inviteLink?: string }) {
|
||||
try {
|
||||
const { recipientEmail, role, words, code, resourceTitle, inviteLink } = params
|
||||
if (!process.env.SMTP_HOST || !process.env.SMTP_USER || !process.env.SMTP_PASSWORD) {
|
||||
return { success: false, error: 'Configuration email manquante' }
|
||||
}
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: parseInt(process.env.SMTP_PORT || '587'),
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
},
|
||||
})
|
||||
|
||||
const wordsHtml = `<p style="font-size:14px;">Mots à saisir: <strong>${words.join(' ')}</strong><br/>Code: <strong>${code}</strong></p>`
|
||||
const resourceHtml = resourceTitle ? `<p style="margin-top:8px;">Contexte: <strong>${resourceTitle}</strong></p>` : ''
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.SMTP_FROM || process.env.SMTP_USER,
|
||||
to: recipientEmail,
|
||||
subject: `Invitation DocV - Rôle: ${role}`,
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<div style="background: #2563eb; color: white; padding: 16px;">
|
||||
<h2 style="margin: 0;">Invitation à rejoindre DocV</h2>
|
||||
</div>
|
||||
<div style="padding: 16px;">
|
||||
<p>Vous avez été invité à rejoindre DocV avec le rôle <strong>${role}</strong>.</p>
|
||||
${resourceHtml}
|
||||
<p>Pour valider votre invitation, saisissez les éléments ci-dessous sur la page de connexion:</p>
|
||||
${wordsHtml}
|
||||
${inviteLink ? `<p>Ou utilisez ce lien sécurisé: <a href="${inviteLink}" style="color:#2563eb;">${inviteLink}</a></p>` : ''}
|
||||
<p>Si le lien ne fonctionne pas, copiez-collez l’URL dans votre navigateur.</p>
|
||||
</div>
|
||||
<div style="background: #f3f4f6; padding: 12px; font-size: 12px; color: #6b7280;">
|
||||
<p>Invitation envoyée ${new Date().toLocaleString('fr-FR')}</p>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
text: `Invitation DocV\nRôle: ${role}\n${resourceTitle ? `Contexte: ${resourceTitle}\n` : ''}Mots: ${words.join(' ')}\nCode: ${code}${inviteLink ? `\nLien: ${inviteLink}` : ''}`,
|
||||
}
|
||||
|
||||
await transporter.sendMail(mailOptions)
|
||||
return { success: true }
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
12
package.json
12
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-v0-project",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
@ -38,6 +38,7 @@
|
||||
"@radix-ui/react-toggle-group": "1.1.1",
|
||||
"@radix-ui/react-tooltip": "1.1.6",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"bip39": "^3.1.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.4",
|
||||
@ -49,17 +50,18 @@
|
||||
"next": "15.2.4",
|
||||
"next-themes": "latest",
|
||||
"nodemailer": "latest",
|
||||
"react": "^19.1.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^19",
|
||||
"react-day-picker": "9.8.0",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-dom": "^19",
|
||||
"react-hook-form": "^7.60.0",
|
||||
"react-qr-code": "^2.0.18",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"recharts": "2.15.4",
|
||||
"sonner": "^1.7.4",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "latest",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "3.25.67"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -74,4 +76,4 @@
|
||||
"tw-animate-css": "1.3.3",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
version_file="VERSION"
|
||||
[[ -f TEMPLATE_VERSION ]] && version_file="TEMPLATE_VERSION"
|
||||
|
||||
[[ -f "$version_file" ]] || { echo "Version file missing ($version_file)"; exit 1; }
|
||||
v=$(tr -d '\r' < "$version_file" | head -n1)
|
||||
[[ -n "$v" ]] || { echo "Empty version"; exit 1; }
|
||||
|
||||
echo "Version file: $version_file=$v"
|
||||
|
||||
if ! grep -Eq "^## \\[$(echo "$v" | sed 's/^v//')\\]" CHANGELOG.md; then
|
||||
echo "CHANGELOG entry for $v not found"; exit 1;
|
||||
fi
|
||||
|
||||
echo "Version alignment OK"
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Release guard script
|
||||
# Checks: tests, docs updated, compile, version ↔ changelog ↔ tag consistency, release type
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
mode="${RELEASE_TYPE:-ci-verify}" # values: latest | wip | ci-verify
|
||||
|
||||
echo "[release-guard] mode=$mode"
|
||||
|
||||
# 1) Basic presence checks
|
||||
[[ -f CHANGELOG.md ]] || { echo "CHANGELOG.md manquant"; exit 1; }
|
||||
version_file="VERSION"
|
||||
[[ -f TEMPLATE_VERSION ]] && version_file="TEMPLATE_VERSION"
|
||||
[[ -f "$version_file" ]] || { echo "$version_file manquant"; exit 1; }
|
||||
|
||||
# 2) Extract version
|
||||
project_version=$(tr -d '\r' < "$version_file" | head -n1 | sed 's/^v//')
|
||||
[[ -n "$project_version" ]] || { echo "Version vide dans $version_file"; exit 1; }
|
||||
echo "[release-guard] version=$project_version"
|
||||
|
||||
# 3) Changelog checks
|
||||
if ! grep -Eq "^## \\[$project_version\\]" CHANGELOG.md; then
|
||||
if [[ "$mode" == "wip" ]]; then
|
||||
grep -Eq "^## \\[Unreleased\\]" CHANGELOG.md || { echo "Section [Unreleased] absente du CHANGELOG"; exit 1; }
|
||||
else
|
||||
echo "Entrée CHANGELOG pour version $project_version manquante"; exit 1;
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4) Tests (optional best-effort)
|
||||
if [[ -x tests/run_all_tests.sh ]]; then
|
||||
echo "[release-guard] exécution tests/run_all_tests.sh"
|
||||
./tests/run_all_tests.sh || { echo "Tests en échec"; exit 1; }
|
||||
else
|
||||
echo "[release-guard] tests absents (ok)"
|
||||
fi
|
||||
|
||||
# 5) Build/compile (optional based on project)
|
||||
if [[ -d sdk_relay ]] && command -v cargo >/dev/null 2>&1; then
|
||||
echo "[release-guard] cargo build (sdk_relay)"
|
||||
(cd sdk_relay && cargo build --quiet) || { echo "Compilation échouée"; exit 1; }
|
||||
else
|
||||
echo "[release-guard] build spécifique non applicable (ok)"
|
||||
fi
|
||||
|
||||
# 6) Release type handling
|
||||
case "$mode" in
|
||||
latest)
|
||||
;;
|
||||
wip)
|
||||
# En wip, autoriser versions suffixées; pas d’exigence d’entrée datée
|
||||
;;
|
||||
ci-verify)
|
||||
# En CI, on valide juste la présence de CHANGELOG et version
|
||||
;;
|
||||
*)
|
||||
echo "RELEASE_TYPE invalide: $mode (latest|wip|ci-verify)"; exit 1;
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "[release-guard] OK"
|
||||
|
||||
@ -1,152 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Script d'automatisation des push SSH (template Linux)
|
||||
# Utilise automatiquement la clé SSH pour pousser sur le remote courant via SSH.
|
||||
|
||||
GITEA_HOST="${GITEA_HOST:-git.4nkweb.com}"
|
||||
|
||||
echo "🔑 Configuration SSH pour push (template)..."
|
||||
|
||||
# Configuration SSH automatique
|
||||
echo "⚙️ Configuration Git pour utiliser SSH..."
|
||||
git config --global url."git@${GITEA_HOST}:".insteadOf "https://${GITEA_HOST}/"
|
||||
|
||||
# Vérifier la configuration SSH
|
||||
echo "🔍 Vérification de la configuration SSH..."
|
||||
if ! ssh -T git@"${GITEA_HOST}" 2>&1 | grep -qi "authenticated\|welcome"; then
|
||||
echo "❌ Échec de l'authentification SSH"
|
||||
echo "💡 Vérifiez que votre clé SSH est configurée :"
|
||||
echo " 1. ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_4nk"
|
||||
echo " 2. Ajouter la clé publique à votre compte Gitea"
|
||||
echo " 3. ssh-add ~/.ssh/id_ed25519_4nk"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Authentification SSH réussie"
|
||||
|
||||
# Fonction pour push automatique
|
||||
auto_push() {
|
||||
local branch=${1:-$(git branch --show-current)}
|
||||
local commit_message=${2:-"Auto-commit $(date '+%Y-%m-%d %H:%M:%S')"}
|
||||
|
||||
echo "🚀 Push automatique sur la branche: $branch"
|
||||
|
||||
# Ajouter tous les changements
|
||||
git add .
|
||||
|
||||
# Ne pas commiter si rien à commiter
|
||||
if [[ -z "$(git diff --cached --name-only)" ]]; then
|
||||
echo "ℹ️ Aucun changement indexé. Skip commit/push."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Commiter avec le message fourni
|
||||
git commit -m "$commit_message" || true
|
||||
|
||||
# Push avec SSH automatique
|
||||
echo "📤 Push vers origin/$branch..."
|
||||
git push origin "$branch"
|
||||
|
||||
echo "✅ Push réussi !"
|
||||
}
|
||||
|
||||
# Fonction pour push avec message personnalisé
|
||||
push_with_message() {
|
||||
local message="$1"
|
||||
local branch=${2:-$(git branch --show-current)}
|
||||
|
||||
echo "💬 Push avec message: $message"
|
||||
auto_push "$branch" "$message"
|
||||
}
|
||||
|
||||
# Fonction pour push rapide (sans message)
|
||||
quick_push() {
|
||||
local branch=${1:-$(git branch --show-current)}
|
||||
auto_push "$branch"
|
||||
}
|
||||
|
||||
# Fonction pour push sur une branche spécifique
|
||||
push_branch() {
|
||||
local branch="$1"
|
||||
local message=${2:-"Update $branch $(date '+%Y-%m-%d %H:%M:%S')"}
|
||||
|
||||
echo "🌿 Push sur la branche: $branch"
|
||||
auto_push "$branch" "$message"
|
||||
}
|
||||
|
||||
# Fonction pour push et merge vers main
|
||||
push_and_merge() {
|
||||
local source_branch=${1:-$(git branch --show-current)}
|
||||
local target_branch=${2:-main}
|
||||
|
||||
echo "🔄 Push et merge $source_branch -> $target_branch"
|
||||
|
||||
# Push de la branche source
|
||||
auto_push "$source_branch"
|
||||
|
||||
# Indication pour PR manuelle
|
||||
echo "🔗 Ouvrez une Pull Request sur votre forge pour $source_branch -> $target_branch"
|
||||
}
|
||||
|
||||
# Fonction pour status et push conditionnel
|
||||
status_and_push() {
|
||||
echo "📊 Statut du repository:"
|
||||
git status --short || true
|
||||
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
echo "📝 Changements détectés, push automatique..."
|
||||
auto_push
|
||||
else
|
||||
echo "✅ Aucun changement à pousser"
|
||||
fi
|
||||
}
|
||||
|
||||
# Menu interactif si aucun argument fourni
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "🤖 Script de push SSH automatique (template)"
|
||||
echo ""
|
||||
echo "Options disponibles:"
|
||||
echo " auto-ssh-push.sh quick - Push rapide"
|
||||
echo " auto-ssh-push.sh message \"Mon message\" - Push avec message"
|
||||
echo " auto-ssh-push.sh branch nom-branche - Push sur branche spécifique"
|
||||
echo " auto-ssh-push.sh merge [source] [target] - Push et préparation merge"
|
||||
echo " auto-ssh-push.sh status - Status et push conditionnel"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Traitement des arguments
|
||||
case "$1" in
|
||||
"quick")
|
||||
quick_push
|
||||
;;
|
||||
"message")
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "❌ Message requis pour l'option 'message'"
|
||||
exit 1
|
||||
fi
|
||||
push_with_message "$2" "${3:-}"
|
||||
;;
|
||||
"branch")
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "❌ Nom de branche requis pour l'option 'branch'"
|
||||
exit 1
|
||||
fi
|
||||
push_branch "$2" "${3:-}"
|
||||
;;
|
||||
"merge")
|
||||
push_and_merge "${2:-}" "${3:-}"
|
||||
;;
|
||||
"status")
|
||||
status_and_push
|
||||
;;
|
||||
*)
|
||||
echo "❌ Option inconnue: $1"
|
||||
echo "💡 Utilisez './scripts/auto-ssh-push.sh' pour voir les options"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "🎯 Push SSH automatique terminé !"
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Script d'initialisation de l'environnement SSH (template Linux)
|
||||
# Configure automatiquement SSH pour les push via Gitea
|
||||
|
||||
GITEA_HOST="${GITEA_HOST:-git.4nkweb.com}"
|
||||
|
||||
echo "🚀 Initialisation de l'environnement SSH (template)..."
|
||||
|
||||
# Couleurs
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
print_status() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
print_status "Configuration SSH..."
|
||||
|
||||
# 1. Configuration Git pour SSH
|
||||
print_status "Configuration Git pour utiliser SSH (${GITEA_HOST})..."
|
||||
git config --global url."git@${GITEA_HOST}:".insteadOf "https://${GITEA_HOST}/"
|
||||
|
||||
# 2. Vérification des clés SSH
|
||||
print_status "Vérification des clés SSH existantes..."
|
||||
if [[ -f ~/.ssh/id_rsa || -f ~/.ssh/id_ed25519 ]]; then
|
||||
print_success "Clé SSH trouvée"
|
||||
else
|
||||
print_warning "Aucune clé SSH trouvée"
|
||||
fi
|
||||
|
||||
# 3. Test de la connexion SSH
|
||||
print_status "Test de la connexion SSH vers ${GITEA_HOST}..."
|
||||
if ssh -T git@"${GITEA_HOST}" 2>&1 | grep -qi "authenticated\|welcome"; then
|
||||
print_success "Authentification SSH réussie"
|
||||
else
|
||||
print_error "Échec de l'authentification SSH"
|
||||
fi
|
||||
|
||||
# 4. Alias Git
|
||||
print_status "Configuration des alias Git..."
|
||||
git config --global alias.ssh-push '!f() { git add . && git commit -m "${1:-Auto-commit $(date)}" && git push origin $(git branch --show-current); }; f'
|
||||
git config --global alias.quick-push '!f() { git add . && git commit -m "Update $(date)" && git push origin $(git branch --show-current); }; f'
|
||||
print_success "Alias Git configurés"
|
||||
|
||||
# 5. Rendu exécutable des scripts si chemin standard
|
||||
print_status "Configuration des permissions des scripts (si présents)..."
|
||||
chmod +x scripts/auto-ssh-push.sh 2>/dev/null || true
|
||||
chmod +x scripts/setup-ssh-ci.sh 2>/dev/null || true
|
||||
print_success "Scripts rendus exécutables (si présents)"
|
||||
|
||||
# 6. Résumé
|
||||
echo ""
|
||||
print_success "=== Configuration SSH terminée ==="
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Script de configuration SSH pour CI/CD (template Linux)
|
||||
# Utilise automatiquement la clé SSH pour les opérations Git
|
||||
|
||||
GITEA_HOST="${GITEA_HOST:-git.4nkweb.com}"
|
||||
|
||||
echo "🔑 Configuration automatique de la clé SSH pour CI/CD..."
|
||||
|
||||
if [ -n "${CI:-}" ]; then
|
||||
echo "✅ Environnement CI détecté"
|
||||
|
||||
if [ -n "${SSH_PRIVATE_KEY:-}" ]; then
|
||||
echo "🔐 Configuration de la clé SSH privée..."
|
||||
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
printf "%s" "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
|
||||
if [ -n "${SSH_PUBLIC_KEY:-}" ]; then
|
||||
printf "%s" "$SSH_PUBLIC_KEY" > ~/.ssh/id_rsa.pub
|
||||
chmod 644 ~/.ssh/id_rsa.pub
|
||||
fi
|
||||
|
||||
cat > ~/.ssh/config << EOF
|
||||
Host ${GITEA_HOST}
|
||||
HostName ${GITEA_HOST}
|
||||
User git
|
||||
IdentityFile ~/.ssh/id_rsa
|
||||
StrictHostKeyChecking no
|
||||
UserKnownHostsFile=/dev/null
|
||||
EOF
|
||||
chmod 600 ~/.ssh/config
|
||||
|
||||
echo "🧪 Test SSH vers ${GITEA_HOST}..."
|
||||
ssh -T git@"${GITEA_HOST}" 2>&1 || true
|
||||
|
||||
git config --global url."git@${GITEA_HOST}:".insteadOf "https://${GITEA_HOST}/"
|
||||
echo "✅ Configuration SSH terminée"
|
||||
else
|
||||
echo "⚠️ SSH_PRIVATE_KEY non défini, bascule HTTPS"
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ Environnement local détecté"
|
||||
if [ -f ~/.ssh/id_rsa ] || [ -f ~/.ssh/id_ed25519 ]; then
|
||||
echo "🔑 Clé SSH locale trouvée"
|
||||
git config --global url."git@${GITEA_HOST}:".insteadOf "https://${GITEA_HOST}/"
|
||||
echo "✅ Configuration SSH locale terminée"
|
||||
else
|
||||
echo "⚠️ Aucune clé SSH trouvée; configuration manuelle requise"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "🎯 Configuration SSH CI/CD terminée"
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "[security-audit] démarrage"
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
rc=0
|
||||
|
||||
# 1) Audit npm (si package.json présent)
|
||||
if [ -f package.json ]; then
|
||||
echo "[security-audit] npm audit --audit-level=moderate"
|
||||
if ! npm audit --audit-level=moderate; then rc=1; fi || true
|
||||
else
|
||||
echo "[security-audit] pas de package.json (ok)"
|
||||
fi
|
||||
|
||||
# 2) Audit Rust (si Cargo.toml présent)
|
||||
if command -v cargo >/dev/null 2>&1 && [ -f Cargo.toml ] || find . -maxdepth 2 -name Cargo.toml | grep -q . ; then
|
||||
echo "[security-audit] cargo audit"
|
||||
if ! cargo audit --deny warnings; then rc=1; fi || true
|
||||
else
|
||||
echo "[security-audit] pas de projet Rust (ok)"
|
||||
fi
|
||||
|
||||
# 3) Recherche de secrets grossiers
|
||||
echo "[security-audit] scan secrets"
|
||||
if grep -RIE "(?i)(api[_-]?key|secret|password|private[_-]?key)" --exclude-dir .git --exclude-dir node_modules --exclude-dir target --exclude "*.md" . >/dev/null 2>&1; then
|
||||
echo "[security-audit] secrets potentiels détectés"; rc=1
|
||||
else
|
||||
echo "[security-audit] aucun secret évident"
|
||||
fi
|
||||
|
||||
echo "[security-audit] terminé rc=$rc"
|
||||
exit $rc
|
||||
@ -1,7 +1,6 @@
|
||||
@import "tailwindcss";
|
||||
@import 'tw-animate-css';
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
|
||||
13
tailwind.config.js
Normal file
13
tailwind.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: 'class', // active le mode sombre via la classe .dark
|
||||
content: [
|
||||
'./app/**/*.{js,ts,jsx,tsx}', // toutes les pages et layouts
|
||||
'./components/**/*.{js,ts,jsx,tsx}' // tous les composants
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
53
tests/test-plan.md
Normal file
53
tests/test-plan.md
Normal file
@ -0,0 +1,53 @@
|
||||
### Plan de tests (fonctionnels et intégration UI)
|
||||
|
||||
Ce document recense les cas de test à couvrir. Il ne fournit pas encore d’implémentation outillée.
|
||||
|
||||
### Authentification 4NK
|
||||
|
||||
- Accès `/login` et affichage de la modal sur soumission d’un identifiant ≠ `1234`
|
||||
- Activation du **mode démo** en saisissant `1234` et redirection vers `/dashboard`
|
||||
- Stockage des tokens en session (mock) et récupération du `pairingId`
|
||||
- Comportement lorsque l’iframe ne répond pas (timeouts et messages d’erreur)
|
||||
|
||||
### Dashboard
|
||||
|
||||
- Affichage des badges « Mode Démo » quand le mock est actif
|
||||
- Rendu des indicateurs de stockage (barres de progression et valeurs)
|
||||
- Chat intégré en tête de page (présence et saisie), conversation globale « My Work » en premier
|
||||
- Navigation latérale (mise en surbrillance de la page active, libellé « My work »)
|
||||
- Intégration du chat dans la page: la section Messages est visible et fonctionnelle (liste + saisie)
|
||||
- Ordre des sections: le chat apparaît avant « Documents récents » et « Activité récente »
|
||||
- Suppression du menu Chat: l’item « Messages » n’apparaît plus dans la sidebar
|
||||
- Indicateur « NEW » visible pour documents/dossiers récents (vue liste et grille)
|
||||
- Indicateurs sous le chat: Stockage permanent, Stockage temporaire, Nouveaux dossiers
|
||||
|
||||
### Formulaire de contact (`/contact`)
|
||||
|
||||
- Validation client (description ≥ 10 caractères)
|
||||
- Envoi: sérialisation des champs vers `FormData`, appel `submitContactForm`
|
||||
- Validation serveur (zod) et gestion des erreurs (messages compréhensibles)
|
||||
- Succès: reset du formulaire et rendu de la carte de confirmation
|
||||
|
||||
### Demande de devis formation (`/formation` → devis)
|
||||
|
||||
- Vérification des champs obligatoires côté serveur (zod)
|
||||
- Envoi de l’email vers `contact@docv.fr`
|
||||
- Gestion des erreurs SMTP (variables manquantes, échec de transport)
|
||||
|
||||
### Emailing (lib/email.ts)
|
||||
|
||||
- Absence de variables SMTP → échec contrôlé et message explicite
|
||||
- Construction des emails HTML et texte (présence des champs, remplacements `\n` → `<br>`)
|
||||
|
||||
### Users (invitations par passphrase)
|
||||
|
||||
- Modale d’invitation: rôle obligatoire, email optionnel, titre document/dossier optionnel
|
||||
- Passphrase visible: 4 mots (liste interne) + code 6 caractères, bouton « Copier »
|
||||
- Lien d’invitation généré et copiable
|
||||
- Envoi d’email optionnel: contenu inclut rôle, contexte, passphrase et lien (si fourni)
|
||||
- Notification de validation: récapitulatif contexte + mots + code
|
||||
|
||||
### Accessibilité et UI
|
||||
|
||||
- Focus management, labels et `aria-` sur les composants d’entrée
|
||||
- Contraste et lisibilité des messages d’erreur/succès
|
||||
Loading…
x
Reference in New Issue
Block a user