Compare commits
287 Commits
create-acc
...
ext
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d179aa704e | ||
|
|
e72b3d56ab | ||
|
|
6733f79b83 | ||
|
|
2d5f731133 | ||
|
|
1a62a955f5 | ||
|
|
8c11c59681 | ||
|
|
b1be8e65ac | ||
|
|
09c2d1f969 | ||
|
|
1b5f672f62 | ||
|
|
390015e823 | ||
|
|
5a8c8e6d4f | ||
|
|
9b5542a3ba | ||
|
|
33853c7a6a | ||
|
|
310428dc42 | ||
|
|
e07aa3474e | ||
|
|
7681c9966b | ||
|
|
2d3ac5a884 | ||
|
|
3f3fdc6b55 | ||
|
|
1bc594fa90 | ||
|
|
81e52c1da4 | ||
|
|
6a9bee3c99 | ||
|
|
67fc87314e | ||
|
|
c1e3a5a588 | ||
|
|
72173caeed | ||
|
|
34d57c95f7 | ||
|
|
8c83757025 | ||
|
|
662b04bfdd | ||
|
|
1542f0fe98 | ||
|
|
9db61039c6 | ||
|
|
f7b30cb495 | ||
|
|
2379af9505 | ||
|
|
18c61fd5e0 | ||
|
|
b7df1bc58e | ||
|
|
4aa19b0b2a | ||
|
|
fd351374fa | ||
|
|
8bba3e5437 | ||
|
|
e341c45639 | ||
|
|
30a3960b2e | ||
|
|
ae58d95e39 | ||
|
|
2bfee359e0 | ||
|
|
4c3328b3db | ||
|
|
e41d17f7f4 | ||
|
|
09c8493aeb | ||
|
|
cb6b4a02da | ||
|
|
1756278094 | ||
|
|
48ea3614b8 | ||
|
|
2d0bbc425c | ||
|
|
0a033dd636 | ||
|
|
c4d35331bd | ||
|
|
85c4106ae6 | ||
|
|
0b57f6c79c | ||
|
|
333101b3f4 | ||
|
|
a302149ea8 | ||
|
|
537e627ebc | ||
|
|
5e3f050132 | ||
|
|
3e53ed70d1 | ||
|
|
b9f435c2bf | ||
|
|
4f7d51e9b2 | ||
|
|
a6f54dc8f5 | ||
|
|
a40416a248 | ||
|
|
c21eb9170a | ||
|
|
d47db27583 | ||
|
|
eb6f6be67b | ||
|
|
e9f6af41a1 | ||
|
|
1c20450b05 | ||
|
|
e76f2e1db9 | ||
|
|
0cf587fa6e | ||
|
|
01b1b50d6b | ||
|
|
5da295be1a | ||
|
|
a4dd8c57f2 | ||
|
|
653b86fbc9 | ||
|
|
70f3a30f64 | ||
|
|
2dda47534b | ||
|
|
01ad5a3816 | ||
|
|
5884e8d845 | ||
|
|
fb54522bce | ||
|
|
59ad93516c | ||
|
|
6ef5186407 | ||
|
|
c89d956821 | ||
|
|
7a52bb2ee7 | ||
|
|
903ad3849d | ||
|
|
17e2cd282e | ||
|
|
e6569cf908 | ||
|
|
0d473cf3d1 | ||
|
|
457994c506 | ||
|
|
5fc485e233 | ||
|
|
0d934e7b6e | ||
|
|
02d28d46bb | ||
|
|
723f4d5d85 | ||
|
|
6f9fa60e2f | ||
|
|
e729e32b35 | ||
|
|
e4681f91e4 | ||
|
|
6363ec1189 | ||
|
|
c8ac815e2b | ||
|
|
ef31cba983 | ||
|
|
47c7d31249 | ||
|
|
ede8d95fd1 | ||
|
|
0fc7b6e4c3 | ||
|
|
3f64369852 | ||
|
|
e8c2d1a05a | ||
|
|
63ee4ce719 | ||
|
|
e0e186f4f4 | ||
|
|
bfca596e8b | ||
|
|
acb9739a80 | ||
|
|
c422881cd1 | ||
|
|
19da967605 | ||
|
|
d4223ce604 | ||
|
|
420979e63e | ||
|
|
1c92a40984 | ||
|
|
046eef18e6 | ||
|
|
2ba7be8dbb | ||
|
|
77d9c1ad43 | ||
|
|
3ce412d814 | ||
|
|
7100eda272 | ||
|
|
1a3a2dbef1 | ||
|
|
76a1d38e09 | ||
|
|
8a0a8e2df2 | ||
|
|
48194dd2de | ||
|
|
8e9d7f0c76 | ||
|
|
eda7102ded | ||
| ec99d101ab | |||
|
|
0dd928d28b | ||
|
|
5ba45a29be | ||
|
|
8541427b87 | ||
| 7b86318dec | |||
|
|
205796d22a | ||
| b072495cea | |||
|
|
9a601056b7 | ||
|
|
d3e207c6da | ||
|
|
cb5297e6fe | ||
|
|
f0151fa55e | ||
|
|
5192745a48 | ||
|
|
a027004bd0 | ||
|
|
aae11200d4 | ||
|
|
dbb7f67154 | ||
|
|
58fed7a53b | ||
|
|
19b2ab994e | ||
|
|
93d610e942 | ||
|
|
1dad1d4e2b | ||
|
|
5a98fac745 | ||
|
|
18d46531a0 | ||
|
|
62ccfec315 | ||
|
|
e9fc0b8454 | ||
|
|
5119d04243 | ||
|
|
5a8c31df32 | ||
|
|
deebcefc3d | ||
|
|
d9b8817ecc | ||
|
|
d8c2b22c3d | ||
|
|
39f24114e1 | ||
|
|
189bd3d252 | ||
|
|
989263d44a | ||
|
|
7391a08a01 | ||
|
|
4e109e8fba | ||
|
|
13b605a850 | ||
|
|
0a860bd559 | ||
|
|
a8b0248b5f | ||
|
|
0dc3c83c3c | ||
|
|
1a87a4db14 | ||
|
|
67cd7a1662 | ||
|
|
44f0d8c6c9 | ||
|
|
10589b056f | ||
|
|
926f41d270 | ||
|
|
7c39795cef | ||
|
|
207b308173 | ||
|
|
337a6adc60 | ||
|
|
d8422de94e | ||
|
|
9edcc2e897 | ||
|
|
f5fae245e2 | ||
| ed4fa732f7 | |||
| ac11893e93 | |||
| 929e7ee36d | |||
| c2a4b598a7 | |||
| 2bd2fdff98 | |||
| 13731da7e1 | |||
|
|
965f5da9a9 | ||
|
|
18ef18db71 | ||
|
|
50a92995d7 | ||
|
|
17bdcec317 | ||
|
|
25caed410e | ||
|
|
cf57681c31 | ||
|
|
91ba7205cc | ||
|
|
d31e18d4ae | ||
|
|
6076c342f8 | ||
|
|
bb5d3ff16d | ||
|
|
a3fe29e4a0 | ||
|
|
0d51f9d056 | ||
|
|
c0d402b234 | ||
|
|
dfae77de58 | ||
|
|
e1494d5bf4 | ||
|
|
ed23adf8f1 | ||
|
|
2a7c0d6675 | ||
|
|
25dba4e67b | ||
|
|
65d43686cb | ||
|
|
18e82de549 | ||
| f4d8f8652f | |||
| 39f2b086b5 | |||
| 00bc3d8ad2 | |||
| b52ff937f0 | |||
| d6e06f3594 | |||
| 05f13224fa | |||
| 06295fe591 | |||
| 72d43210de | |||
| 73cee5d144 | |||
| 85fe8cc251 | |||
| ec9fe0f62c | |||
| b6a2a5fc3b | |||
| 7417aec7e0 | |||
| f42aca7eb9 | |||
| 0f0b5d1af3 | |||
| 84aa6298e3 | |||
| 14b539595f | |||
| 99400a71f7 | |||
| c5b58d999f | |||
| 23a3b2a9e8 | |||
| 6167d59501 | |||
| b828e5197a | |||
| 26ba3e6e93 | |||
| df726d929a | |||
| 0e44a01218 | |||
| 8260c6c5da | |||
| 8eb6f36b64 | |||
| e15da5c22a | |||
| a8b3631dc1 | |||
| 89e9b3e4e0 | |||
| c4db22f626 | |||
| accd427cab | |||
| 381dcdf7a8 | |||
| 0cbc07cf63 | |||
| 3c59105aa6 | |||
| 325d2cbf13 | |||
| d4f1f36376 | |||
| f6edadc535 | |||
| 0099a8c858 | |||
| 0e0c3946d2 | |||
| 0a2a2674f8 | |||
| 9d461d63d7 | |||
| 2f68c652dd | |||
| 147f4cfa7d | |||
| 235aecd6a7 | |||
| e1f2483924 | |||
| 0c2df347ec | |||
| abfe581f29 | |||
| b66ee42ddd | |||
| aecdcd93e1 | |||
| c63e2a6fe9 | |||
| 67963bfb02 | |||
| 4b12b560e1 | |||
| 28c151254c | |||
| 5d0c617bbb | |||
| ae88959496 | |||
| e5a958b0b9 | |||
| 6b77ec2972 | |||
| a1ce472cad | |||
| db48386f05 | |||
| 39b50d6789 | |||
| 86393e6cfa | |||
| bf06b6634a | |||
| cfc9514656 | |||
| 0f364c7c6e | |||
| ee7c79a7d5 | |||
| 37bdb3dad3 | |||
| ecba13594b | |||
| 4c534973d2 | |||
| eca4d4de85 | |||
| 824a0b88f6 | |||
| e224921f86 | |||
| cf18e46e17 | |||
| e6cf1c3658 | |||
| b9851c587e | |||
| d601d94bf6 | |||
| d19ba72b4a | |||
| 2d0e15533a | |||
| 94ee8842e3 | |||
| 7b7d13ce6c | |||
| cc9396c4b8 | |||
| 51c906866e | |||
| 3f42cb27a7 | |||
| 2601418aaf | |||
| 455fe53fe2 | |||
| 2f847514f0 | |||
| a0888f8c90 | |||
| f2e2aeaa9a | |||
| 2855365851 | |||
| bb277706fd | |||
| 05dddd9567 | |||
| d54ce71f02 | |||
| a42141246d |
2
.ci-trigger
Normal file
@ -0,0 +1,2 @@
|
||||
# CI Trigger
|
||||
# CI Trigger Sun Sep 21 19:57:46 UTC 2025
|
||||
95
.cursorignore
Normal file
@ -0,0 +1,95 @@
|
||||
# 4NK Environment - Git Ignore
|
||||
# ============================
|
||||
confs/
|
||||
# Dossiers de sauvegarde des scripts
|
||||
**/backup/
|
||||
**/*backup*
|
||||
|
||||
**/.cargo/
|
||||
|
||||
# Fichiers temporaires
|
||||
**/*.tmp*
|
||||
**/*.temp*
|
||||
**/*.log*
|
||||
**/*.pid*
|
||||
|
||||
# Fichiers de configuration locale
|
||||
**/*.env*
|
||||
**/*.conf*
|
||||
**/*.yaml*
|
||||
**/*.yml*
|
||||
**/*.ini*
|
||||
**/*.json*
|
||||
**/*.toml*
|
||||
**/*.lock*
|
||||
|
||||
# Données et logs
|
||||
**/*.logs*
|
||||
**/*.data
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# Certificats et clés
|
||||
**/*.key
|
||||
**/*.pem
|
||||
**/*.crt
|
||||
**/*.p12
|
||||
**/*.pfx
|
||||
ssl/
|
||||
certs/
|
||||
|
||||
# Docker
|
||||
**/*.docker*
|
||||
|
||||
# Cache et build
|
||||
**/node_modules/
|
||||
**/dist/
|
||||
**/build/
|
||||
**/target/
|
||||
**/.next/
|
||||
**/.turbo/
|
||||
**/coverage/
|
||||
**/.pytest_cache/
|
||||
**/.cache/
|
||||
**/.pnpm-store/
|
||||
**/.venv/
|
||||
**/vendor/
|
||||
**/*.*.o
|
||||
**/*.so
|
||||
**/*.dylib
|
||||
|
||||
# IDE et éditeurs
|
||||
**/*.vscode/
|
||||
**/*.idea/
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
**/*~
|
||||
|
||||
# OS
|
||||
**/*.DS_Store
|
||||
**/*Thumbs.db
|
||||
**/*tmp*
|
||||
|
||||
# Git
|
||||
**/*.git/
|
||||
**/*.orig*
|
||||
|
||||
# Backup des projets existants
|
||||
**/*backup*
|
||||
**/backups/
|
||||
**/*backups*
|
||||
|
||||
|
||||
**/*wallet*
|
||||
**/*keys*
|
||||
|
||||
**/*node_modules*
|
||||
**/*cursor*
|
||||
**/*pid*
|
||||
**/*next*
|
||||
|
||||
# Dossiers de logs communs
|
||||
log/
|
||||
logs/
|
||||
**/log/
|
||||
**/logs/
|
||||
165
.cursorrules
Normal file
@ -0,0 +1,165 @@
|
||||
# Règles globales Cursor pour les projets
|
||||
|
||||
## Principes généraux
|
||||
- Lire impérativement le fichier `.cursorrules` au démarrage de chaque session.
|
||||
- Lire tous les fichiers du dossier `docs/`, le code et les paramètres avant de commencer.
|
||||
- Poser des questions et proposer des améliorations si nécessaire.
|
||||
- Ajouter les leçons apprises à ce fichier `.cursorrules`.
|
||||
- Écrire des documents complets et exhaustifs.
|
||||
- Respecter strictement les règles de lint du Markdown.
|
||||
- Préférer toujours un shell **bash** à PowerShell.
|
||||
- Fermer et relancer le terminal avant chaque utilisation.
|
||||
- Si le terminal est interrompu, analyser la commande précédente (interruption probablement volontaire).
|
||||
- Exécuter automatiquement les étapes de résolution de problème.
|
||||
- Expliquer les commandes complexes avant de les lancer.
|
||||
- Compiler régulièrement et corriger toutes les erreurs avant de passer à l’étape suivante.
|
||||
- Tester, documenter, compiler, aligner tag git, changelog et version avant déploiement et push.
|
||||
- Utiliser `docx2txt` pour lire les fichiers `.docx`.
|
||||
- Ajouter automatiquement les dépendances et rechercher systématiquement les dernières versions.
|
||||
- Faire des commandes simples et claires en plusieurs étapes.
|
||||
- Vérifie toujours tes hypothèses avant de commencer.
|
||||
- N'oublie jamais qu'après la correction d'un problème, il faut corriger toutes les erreurs qui peuvent en découler.
|
||||
|
||||
## Organisation des fichiers et répertoires
|
||||
- Scripts regroupés dans `scripts/`
|
||||
- Configurations regroupées dans `confs/`
|
||||
- Journaux regroupés dans `logs/`
|
||||
- Répertoires obligatoires :
|
||||
- `docs/` : documentation de toute fonctionnalité ajoutée, modifiée, supprimée ou découverte.
|
||||
- `tests/` : tests liés à toute fonctionnalité ajoutée, modifiée, supprimée ou découverte.
|
||||
- Remplacer les résumés (`RESUME`) par des mises à jour dans `docs/`.
|
||||
|
||||
## Configuration critique des services
|
||||
- Mempool du réseau signet :
|
||||
`https://mempool2.4nkweb.com/fr/docs/api/rest`
|
||||
|
||||
## Développement et sécurité
|
||||
- Ne jamais committer de clés privées ou secrets.
|
||||
- Utiliser des variables d’environnement pour les données sensibles.
|
||||
- Définir correctement les dépendances Docker avec healthchecks.
|
||||
- Utiliser les URLs de service Docker Compose (`http://service_name:port`).
|
||||
- Documenter toutes les modifications importantes dans `docs/`.
|
||||
- Externaliser au maximum les variables d’environnement.
|
||||
- Toujours utiliser une clé SSH pour cloner les dépôts.
|
||||
- Monter en version les dépôts au début du travail.
|
||||
- Pousser les tags docker `ext` via la CI sur `git.4nkweb.com`.
|
||||
- Corriger systématiquement les problèmes, même mineurs, sans contournement.
|
||||
|
||||
## Scripts (règles critiques)
|
||||
- Vérifier l’existence d’un script dans `scripts/` avant toute action.
|
||||
- Utiliser les scripts existants plutôt que des commandes directes.
|
||||
- Ne jamais créer plusieurs versions ou noms de scripts.
|
||||
- Améliorer l’existant au lieu de créer des variantes (`startup-v2.sh`, etc.).
|
||||
|
||||
## Images Docker (règles critiques)
|
||||
- Ajouter systématiquement `apt update && apt upgrade` dans les Dockerfiles.
|
||||
- Installer en arrière-plan dans les images Docker :
|
||||
`curl, git, sed, awk, nc, wget, jq, telnet, tee, wscat, ping, npm (dernière version)`
|
||||
- Appliquer à tous les Dockerfiles et `docker-compose.yml`.
|
||||
- N'utilise pas les version test ou dev ou ext-dev mais toujours les version ext, relance leur compilation si nécessaire
|
||||
|
||||
## Fichiers de configuration (règles critiques)
|
||||
- Vérifier l’écriture effective après chaque modification.
|
||||
- Fichiers concernés : `nginx.conf`, `bitcoin.conf`, `package.json`, `Cargo.toml`.
|
||||
- Utiliser `cat`, `jq` ou vérificateurs de syntaxe.
|
||||
|
||||
## Connexion au réseau Bitcoin signet
|
||||
Commande unique et obligatoire :
|
||||
```bash
|
||||
docker exec bitcoin-signet bitcoin-cli -signet -rpccookiefile=/home/bitcoin/.bitcoin/signet/.cookie getblockchaininfo
|
||||
````
|
||||
|
||||
## Connexion au relay/faucet bootstrap
|
||||
|
||||
* Test via WSS : `wss://dev3.4nkweb.com/ws/`
|
||||
* Envoi Faucet, réponse attendue avec `NewTx` (tx hex et tweak\_data).
|
||||
|
||||
## Debug
|
||||
|
||||
* Automatiser dans le code toute solution validée.
|
||||
* Pérenniser les retours d’expérience dans code et paramètres.
|
||||
* Compléter les tests pour éviter les régressions.
|
||||
|
||||
## Nginx
|
||||
|
||||
* Tous les fichiers dans `conf/ngnix` doivent être mappés avec ceux du serveur.
|
||||
|
||||
## Minage (règles critiques)
|
||||
|
||||
* Toujours valider les adresses utilisées (adresses TSP non reconnues).
|
||||
* Utiliser uniquement des adresses Bitcoin valides (bech32m).
|
||||
* Vérifier que le minage génère des blocs avec transactions, pas uniquement coinbase.
|
||||
* Surveiller les logs du minage pour détecter les erreurs d’adresse.
|
||||
* Vérifier la propagation via le mempool externe.
|
||||
|
||||
## Mempool externe
|
||||
|
||||
* Utiliser `https://mempool2.4nkweb.com` pour vérifier les transactions.
|
||||
* Vérifier la synchronisation entre réseau local et externe.
|
||||
|
||||
## Données et modèles
|
||||
|
||||
* Utiliser les fichiers CSV comme base des modèles de données.
|
||||
* Être attentif aux en-têtes multi-lignes.
|
||||
* Confirmer la structure comprise et demander définition de toutes les colonnes.
|
||||
* Corriger automatiquement incohérences de type.
|
||||
|
||||
## Implémentation et architecture
|
||||
|
||||
* Code splitting avec `React.lazy` et `Suspense`.
|
||||
* Centraliser l’état avec Redux ou Context API.
|
||||
* Créer une couche d’abstraction pour les services de données.
|
||||
* Aller systématiquement au bout d’une implémentation.
|
||||
|
||||
## Préparation open source
|
||||
|
||||
Chaque projet doit être prêt pour un dépôt sur `git.4nkweb.com` :
|
||||
|
||||
* Inclure : `LICENSE` (MIT, Apache 2.0 ou GPL), `CONTRIBUTING.md`, `CHANGELOG.md`, `CODE_OF_CONDUCT.md`.
|
||||
* Aligner documentation et tests avec `4NK_node`.
|
||||
|
||||
## Versioning et documentation
|
||||
|
||||
* Mettre à jour documentation et tests systématiquement.
|
||||
* Gérer versioning avec changelog.
|
||||
* Demander validation avant tag.
|
||||
* Documenter les hypothèses testées dans un REX technique.
|
||||
* Tester avant tout commit.
|
||||
* Tester les buildsavant tout tag.
|
||||
|
||||
## Bonnes pratiques de confidentialité et sécurité
|
||||
|
||||
### Docker
|
||||
- Ne jamais stocker de secrets (clés, tokens, mots de passe) dans les Dockerfiles ou docker-compose.yml.
|
||||
- Utiliser des fichiers `.env` sécurisés (non commités avec copie en .env.example) pour toutes les variables sensibles.
|
||||
- Ne pas exécuter de conteneurs avec l’utilisateur root, privilégier un utilisateur dédié.
|
||||
- Limiter les capacités des conteneurs (option `--cap-drop`) pour réduire la surface d’attaque.
|
||||
- Scanner régulièrement les images Docker avec un outil de sécurité (ex : Trivy, Clair).
|
||||
- Mettre à jour en continu les images de base afin d’éliminer les vulnérabilités.
|
||||
- Ne jamais exposer de ports inutiles.
|
||||
- Restreindre les volumes montés au strict nécessaire.
|
||||
- Utiliser des réseaux Docker internes pour la communication inter-containers.
|
||||
- Vérifier et tenir à jour les .dockerignore.
|
||||
|
||||
### Git
|
||||
- Ne jamais committer de secrets, clés ou identifiants (même temporairement).
|
||||
- Configurer des hooks Git (pre-commit) pour détecter automatiquement les secrets et les failles.
|
||||
- Vérifier l’historique (`git log`, `git filter-repo`) pour s’assurer qu’aucune information sensible n’a été poussée.
|
||||
- Signer les commits avec GPG pour garantir l’authenticité.
|
||||
- Utiliser systématiquement SSH pour les connexions à distance.
|
||||
- Restreindre les accès aux dépôts (principes du moindre privilège).
|
||||
- Documenter les changements sensibles dans `CHANGELOG.md`.
|
||||
- Ne jamais pousser directement sur `main` ou `master`, toujours passer par des branches de feature ou PR.
|
||||
- Vérifier et tenir à jour les .gitignore.
|
||||
- Vérifier et tenir à jour les .gitkeep.
|
||||
- Vérifier et tenir à jour les .gitattributes.
|
||||
|
||||
### Cursor
|
||||
- Toujours ouvrir une session en commençant par relire le fichier `.cursorrules`.
|
||||
- Vérifier que Cursor ne propose pas de commit contenant des secrets ou fichiers sensibles.
|
||||
- Ne pas exécuter dans Cursor de commandes non comprises ou copiées sans vérification.
|
||||
- Préférer l’utilisation de scripts audités dans `scripts/` plutôt que des commandes directes dans Cursor.
|
||||
- Fermer et relancer Cursor régulièrement pour éviter des contextes persistants non désirés.
|
||||
- Ne jamais partager le contenu du terminal ou des fichiers sensibles via Cursor en dehors du périmètre du projet.
|
||||
- Vérifier et tenir à jour les .cursorrules.
|
||||
- Vérifier et tenir à jour les .cursorignore.
|
||||
95
.dockerignore
Normal file
@ -0,0 +1,95 @@
|
||||
# 4NK Environment - Git Ignore
|
||||
# ============================
|
||||
confs/
|
||||
# Dossiers de sauvegarde des scripts
|
||||
**/backup/
|
||||
**/*backup*
|
||||
|
||||
**/.cargo/
|
||||
|
||||
# Fichiers temporaires
|
||||
**/*.tmp*
|
||||
**/*.temp*
|
||||
**/*.log*
|
||||
**/*.pid*
|
||||
|
||||
# Fichiers de configuration locale
|
||||
**/*.env*
|
||||
**/*.conf*
|
||||
**/*.yaml*
|
||||
**/*.yml*
|
||||
**/*.ini*
|
||||
**/*.json*
|
||||
**/*.toml*
|
||||
**/*.lock*
|
||||
|
||||
# Données et logs
|
||||
**/*.logs*
|
||||
**/*.data
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# Certificats et clés
|
||||
**/*.key
|
||||
**/*.pem
|
||||
**/*.crt
|
||||
**/*.p12
|
||||
**/*.pfx
|
||||
ssl/
|
||||
certs/
|
||||
|
||||
# Docker
|
||||
**/*.docker*
|
||||
|
||||
# Cache et build
|
||||
**/node_modules/
|
||||
**/dist/
|
||||
**/build/
|
||||
**/target/
|
||||
**/.next/
|
||||
**/.turbo/
|
||||
**/coverage/
|
||||
**/.pytest_cache/
|
||||
**/.cache/
|
||||
**/.pnpm-store/
|
||||
**/.venv/
|
||||
**/vendor/
|
||||
**/*.*.o
|
||||
**/*.so
|
||||
**/*.dylib
|
||||
|
||||
# IDE et éditeurs
|
||||
**/*.vscode/
|
||||
**/*.idea/
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
**/*~
|
||||
|
||||
# OS
|
||||
**/*.DS_Store
|
||||
**/*Thumbs.db
|
||||
**/*tmp*
|
||||
|
||||
# Git
|
||||
**/*.git/
|
||||
**/*.orig*
|
||||
|
||||
# Backup des projets existants
|
||||
**/*backup*
|
||||
**/backups/
|
||||
**/*backups*
|
||||
|
||||
|
||||
**/*wallet*
|
||||
**/*keys*
|
||||
|
||||
**/*node_modules*
|
||||
**/*cursor*
|
||||
**/*pid*
|
||||
**/*next*
|
||||
|
||||
# Dossiers de logs communs
|
||||
log/
|
||||
logs/
|
||||
**/log/
|
||||
**/logs/
|
||||
3
.env
@ -1,3 +0,0 @@
|
||||
# .env
|
||||
VITE_API_URL=https://api.example.com
|
||||
VITE_API_KEY=your_api_key
|
||||
42
.gitea/workflows/docker-build.yml
Normal file
@ -0,0 +1,42 @@
|
||||
name: Docker Build and Push (ext)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- ext
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Derive DOCKER_TAG from commit message or fallback
|
||||
id: tag
|
||||
run: |
|
||||
msg=$(git log -1 --pretty=%B | tr -d '\r')
|
||||
tag=$(printf '%s' "$msg" | sed -n 's/.*docker_tag=\([A-Za-z0-9._-]\+\).*/\1/p' | head -n1)
|
||||
if [ -z "$tag" ]; then tag=ext; fi
|
||||
tag=$(printf '%s' "$tag" | tr -d '[:space:]')
|
||||
echo "DOCKER_TAG=$tag" >> $GITHUB_ENV
|
||||
echo "tag=$tag" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log in to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.4nkweb.com
|
||||
username: ${{ secrets.USER }}
|
||||
password: ${{ secrets.TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: |
|
||||
git.4nkweb.com/4nk/ihm_client:${{ env.DOCKER_TAG }}
|
||||
69
.gitea/workflows/docker-ext.yml
Normal file
@ -0,0 +1,69 @@
|
||||
name: Build and Push Docker image (ext)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- ext
|
||||
|
||||
env:
|
||||
REGISTRY: git.4nkweb.com
|
||||
IMAGE_NAMESPACE: 4nk
|
||||
IMAGE_NAME: ihm_client
|
||||
DOCKER_TAG: ext
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.USER }}
|
||||
password: ${{ secrets.TOKEN }}
|
||||
|
||||
- name: Setup SSH agent for git clone
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: |
|
||||
${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Verify WebAssembly files
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Verify that the pre-built WASM files are present
|
||||
echo "Verifying WebAssembly files..."
|
||||
ls -la pkg/
|
||||
file pkg/sdk_client_bg.wasm
|
||||
echo "WebAssembly files verified successfully"
|
||||
|
||||
- name: Extract docker tag from commit message (optional)
|
||||
id: tag
|
||||
run: |
|
||||
set -euo pipefail
|
||||
MSG=$(git log -1 --pretty=%B | tr -d '\r')
|
||||
if echo "$MSG" | grep -q "ci: docker_tag="; then
|
||||
T=$(echo "$MSG" | sed -nE 's/.*ci: docker_tag=([^ ]+).*/\1/p' | tr -d '\n')
|
||||
if [ -n "$T" ]; then
|
||||
echo "tag=$T" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag || env.DOCKER_TAG }}
|
||||
ssh: default
|
||||
102
.gitignore
vendored
@ -1,7 +1,95 @@
|
||||
target/
|
||||
pkg/
|
||||
Cargo.lock
|
||||
node_modules/
|
||||
dist/
|
||||
.vscode
|
||||
public/ssl/
|
||||
# 4NK Environment - Git Ignore
|
||||
# ============================
|
||||
confs/
|
||||
# Dossiers de sauvegarde des scripts
|
||||
**/backup/
|
||||
**/*backup*
|
||||
|
||||
**/.cargo/
|
||||
|
||||
# Fichiers temporaires
|
||||
**/*.tmp*
|
||||
**/*.temp*
|
||||
**/*.log*
|
||||
**/*.pid*
|
||||
|
||||
# Fichiers de configuration locale
|
||||
**/*.env*
|
||||
**/*.conf*
|
||||
**/*.yaml*
|
||||
**/*.yml*
|
||||
**/*.ini*
|
||||
**/*.json*
|
||||
**/*.toml*
|
||||
**/*.lock*
|
||||
|
||||
# Données et logs
|
||||
**/*.logs*
|
||||
**/*.data
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# Certificats et clés
|
||||
**/*.key
|
||||
**/*.pem
|
||||
**/*.crt
|
||||
**/*.p12
|
||||
**/*.pfx
|
||||
ssl/
|
||||
certs/
|
||||
|
||||
# Docker
|
||||
**/*.docker*
|
||||
|
||||
# Cache et build
|
||||
**/node_modules/
|
||||
**/dist/
|
||||
**/build/
|
||||
**/target/
|
||||
**/.next/
|
||||
**/.turbo/
|
||||
**/coverage/
|
||||
**/.pytest_cache/
|
||||
**/.cache/
|
||||
**/.pnpm-store/
|
||||
**/.venv/
|
||||
**/vendor/
|
||||
**/*.*.o
|
||||
**/*.so
|
||||
**/*.dylib
|
||||
|
||||
# IDE et éditeurs
|
||||
**/*.vscode/
|
||||
**/*.idea/
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
**/*~
|
||||
|
||||
# OS
|
||||
**/*.DS_Store
|
||||
**/*Thumbs.db
|
||||
**/*tmp*
|
||||
|
||||
# Git
|
||||
**/*.git/
|
||||
**/*.orig*
|
||||
|
||||
# Backup des projets existants
|
||||
**/*backup*
|
||||
**/backups/
|
||||
**/*backups*
|
||||
|
||||
|
||||
**/*wallet*
|
||||
**/*keys*
|
||||
|
||||
**/*node_modules*
|
||||
**/*cursor*
|
||||
**/*pid*
|
||||
**/*next*
|
||||
|
||||
# Dossiers de logs communs
|
||||
log/
|
||||
logs/
|
||||
**/log/
|
||||
**/logs/
|
||||
17
CHANGELOG.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
### Documentation WebSocket et architecture
|
||||
- **Documentation WebSocket** : Ajout de `docs/WEBSOCKET_CONNECTION.md` avec analyse complète de l'architecture
|
||||
- **Architecture de l'iframe** : Documentation de la logique de fonctionnement (init, services, WebSocket)
|
||||
- **Configuration WebSocket** : Documentation des variables d'environnement et connexions
|
||||
- **Gestion des messages** : Documentation du handshake et mise à jour des adresses relay
|
||||
- **Communication parent** : Documentation de l'écoute des messages et gestion des erreurs
|
||||
- **Problème persistant** : 502 Bad Gateway - Nginx ne transmet pas les headers WebSocket
|
||||
|
||||
## [1.0.0]
|
||||
### Version initiale
|
||||
- Interface utilisateur pour LeCoffre
|
||||
- Intégration WebSocket avec les relays
|
||||
- Gestion des processus et authentification
|
||||
- Communication avec le parent via postMessage
|
||||
57
Dockerfile
@ -1,13 +1,44 @@
|
||||
FROM node:20
|
||||
|
||||
ENV TZ=Europe/Paris
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
# use this user because he have uid et gid 1000 like theradia
|
||||
USER node
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
CMD ["npm", "start"]
|
||||
# "--disable-host-check", "--host", "0.0.0.0", "--ssl", "--ssl-cert", "/ssl/certs/site.crt", "--ssl-key", "/ssl/private/site.dec.key"]
|
||||
|
||||
# syntax=docker/dockerfile:1.4
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
WORKDIR /app
|
||||
|
||||
# Installation des dépendances minimales nécessaires
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Création d'un utilisateur non-root
|
||||
RUN useradd -m -u 1000 appuser && \
|
||||
mkdir -p /app && chown -R appuser:appuser /app
|
||||
|
||||
# Copy project files
|
||||
COPY . .
|
||||
RUN chown -R appuser:appuser /app
|
||||
|
||||
# Ensure pkg directory exists and has correct permissions
|
||||
RUN mkdir -p pkg && chmod -R 755 pkg
|
||||
|
||||
# Verify pkg files are present
|
||||
RUN ls -la pkg/
|
||||
|
||||
# Copy the provided prebuilt WASM package (ESM)
|
||||
# The directory pkg is provided in the build context
|
||||
# and already contains sdk_client.js (ES module) and wasm
|
||||
# so no compilation is required here.
|
||||
|
||||
# Installation des dépendances Node.js
|
||||
USER appuser
|
||||
RUN npm install
|
||||
|
||||
EXPOSE 3003
|
||||
|
||||
CMD ["npm", "start"]
|
||||
|
||||
103
README.md
@ -4,49 +4,70 @@
|
||||
|
||||
## HOW TO START
|
||||
|
||||
1 - clone sdk_common, commit name "doc pcd" from 28.10.2024
|
||||
2 - clone sdk_client, commit name "Ignore messages" from 17.10.2024
|
||||
3 - clone ihm_client_test3
|
||||
4 - cargo build in sdk_common
|
||||
5 - cargo run in sdk_client
|
||||
6 - npm run build_wasm in ihm_client_test3
|
||||
7 - npm run start in ihm_client_test3
|
||||
1 - clone sdk_common, commit name "doc pcd" from 28.10.2024
|
||||
2 - clone sdk_client, commit name "Ignore messages" from 17.10.2024
|
||||
3 - clone ihm_client_test3
|
||||
4 - cargo build in sdk_common
|
||||
5 - cargo run in sdk_client
|
||||
6 - npm run build_wasm in ihm_client_test3
|
||||
7 - npm run start in ihm_client_test3
|
||||
|
||||
## USER STORIES
|
||||
|
||||
1 - I can login with my adress device
|
||||
2 - I can login with QR code
|
||||
3 - J'accède à la page Process après ma connexion
|
||||
4 - Dans l'interface Process, je peux sélectionner un processus avec sa zone
|
||||
5 - Je reçois des notifications dans la page Process
|
||||
6 - Dans le menu, je peux importer mes données au format JSON
|
||||
7 - Dans le menu, je peux accèder à la page Account
|
||||
8 - Dans la page Account, je peux cliquer sur mon profil via la bulle de profil en haut à gauche et une popup de profil s'ouvre
|
||||
9 - Dans la popup de profil, je peux voir mes informations personnelles et modifier certaines d'entre elles dont le nom, le prénom
|
||||
10 - Dans la popup de profil, je peux changer ma photo de profil
|
||||
11 - Dans la popup de profil, je peux fermer la popup en cliquant sur le bouton "X" en haut à droite de la popup
|
||||
12 - Dans la popup de profil, je peux cliquer sur le bouton "Export User Data", ce qui me génère un fichier JSON
|
||||
13 - Dans la popup de profil, je peux cliquer sur le bouton "Delete Account", ce qui me demande à valider mon choix
|
||||
14 - Dans la popup de profil, je peux cliquer sur le bouton "Logout", ce qui me déconnecte
|
||||
15 - Dans la popup de profil, je peux cliquer sur le bouton "Export Recovery", ce qui me demandera de confirmer mon choix ou d'annuler, si je confirme, je dois retenir et écrire les 4 mots de récupération, le bouton ne sera plus accessible après cela
|
||||
16 - Dans l'onglet Pairing de la page Account, je peux ajouter un nouveau "device" en cliquant sur le bouton "Add Device"
|
||||
17 - Dans l'onglet Pairing de la page Account, je peux supprimer un "device" en cliquant sur l'emoji de la poubelle à côté du device que je souhaite supprimer
|
||||
18 - Dans l'onglet Pairing de la page Account, je peux cliquer sur le bouton "Scan QR Code" pour scanner le QR Code d'un nouveau device
|
||||
19 - Dans l'onglet Pairing de la page Account, je peux renommer un "Device" en cliquant sur son nom et en modifiant le nom
|
||||
20 - Dans l'onglet Wallet de la page Account, je peux ajouter un nouveau "wallet" en cliquant sur le bouton "Add a line"
|
||||
1 - I can login with my adress device
|
||||
2 - I can login with QR code
|
||||
3 - J'accède à la page Process après ma connexion
|
||||
4 - Dans l'interface Process, je peux sélectionner un processus avec sa zone
|
||||
5 - Je reçois des notifications dans la page Process
|
||||
6 - Dans le menu, je peux importer mes données au format JSON
|
||||
7 - Dans le menu, je peux accèder à la page Account
|
||||
8 - Dans la page Account, je peux cliquer sur mon profil via la bulle de profil en haut à gauche et une popup de profil s'ouvre
|
||||
9 - Dans la popup de profil, je peux voir mes informations personnelles et modifier certaines d'entre elles dont le nom, le prénom
|
||||
10 - Dans la popup de profil, je peux changer ma photo de profil
|
||||
11 - Dans la popup de profil, je peux fermer la popup en cliquant sur le bouton "X" en haut à droite de la popup
|
||||
12 - Dans la popup de profil, je peux cliquer sur le bouton "Export User Data", ce qui me génère un fichier JSON
|
||||
13 - Dans la popup de profil, je peux cliquer sur le bouton "Delete Account", ce qui me demande à valider mon choix
|
||||
14 - Dans la popup de profil, je peux cliquer sur le bouton "Logout", ce qui me déconnecte
|
||||
15 - Dans la popup de profil, je peux cliquer sur le bouton "Export Recovery", ce qui me demandera de confirmer mon choix ou d'annuler, si je confirme, je dois retenir et écrire les 4 mots de récupération, le bouton ne sera plus accessible après cela
|
||||
16 - Dans l'onglet Pairing de la page Account, je peux ajouter un nouveau "device" en cliquant sur le bouton "Add Device"
|
||||
17 - Dans l'onglet Pairing de la page Account, je peux supprimer un "device" en cliquant sur l'emoji de la poubelle à côté du device que je souhaite supprimer
|
||||
18 - Dans l'onglet Pairing de la page Account, je peux cliquer sur le bouton "Scan QR Code" pour scanner le QR Code d'un nouveau device
|
||||
19 - Dans l'onglet Pairing de la page Account, je peux renommer un "Device" en cliquant sur son nom et en modifiant le nom
|
||||
20 - Dans l'onglet Wallet de la page Account, je peux ajouter un nouveau "wallet" en cliquant sur le bouton "Add a line"
|
||||
21 - Dans l'onglet Process de la page Account, je peux voir les Process disponibles et voir leur notifications en cliquant sur sur la sonnette à côté du processus
|
||||
22 - Dans l'onglet Data de la page Account, je peux voir les données importées
|
||||
23 - Je peux voir le contrat associé à une Data en cliquant sur le contrat dans la ligne de la Data
|
||||
24 - Dans le menu je peux accèder à la page Chat
|
||||
25 - Dans la page Chat, je peux voir les Processus
|
||||
26 - Dans les Processus, je peux voir utilisateurs assignés à un rôle
|
||||
27 - Dans les Processus, je peux envoyer des messages et des documents en cliquant sur le nom d'un utilisateur en en envoyant "send"
|
||||
28 - Dans le menu je peux accèder à la page "Signatures"
|
||||
29 - Je peux voir les documents à signer et vierge en cliquand sur l'emoji ⚙️ à côté du processus
|
||||
30 - En cliquand sur l'onglet d'un processus, je peux voir les rôles assignés à un utilisateur en cliquant dessus
|
||||
31 - En cliquand sur l'emoji 📁 à côté d'un rôle, je peux voir les documents associés à ce rôle
|
||||
32 - Dans la vue des documents associés à un rôle, je peux créer un évènement de nouvelle signature pour tous les rôles associés à ce Processus, avec le bouton "New Request"
|
||||
33 - En cliquant sur le bouton "New Request", une nouvelle fenêtre s'ouvre pour me permettre de rentrer la description, la visibilité, la date d'échéance, importer des documents, voir les signataires et "Request"
|
||||
34 - Dans le menu, je peux me déconnecter avec le bouton "Disconnect"
|
||||
22 - Dans l'onglet Data de la page Account, je peux voir les données importées
|
||||
23 - Je peux voir le contrat associé à une Data en cliquant sur le contrat dans la ligne de la Data
|
||||
24 - Dans le menu je peux accèder à la page Chat
|
||||
25 - Dans la page Chat, je peux voir les Processus
|
||||
26 - Dans les Processus, je peux voir utilisateurs assignés à un rôle
|
||||
27 - Dans les Processus, je peux envoyer des messages et des documents en cliquant sur le nom d'un utilisateur en en envoyant "send"
|
||||
28 - Dans le menu je peux accèder à la page "Signatures"
|
||||
29 - Je peux voir les documents à signer et vierge en cliquand sur l'emoji ⚙️ à côté du processus
|
||||
30 - En cliquand sur l'onglet d'un processus, je peux voir les rôles assignés à un utilisateur en cliquant dessus
|
||||
31 - En cliquand sur l'emoji 📁 à côté d'un rôle, je peux voir les documents associés à ce rôle
|
||||
32 - Dans la vue des documents associés à un rôle, je peux créer un évènement de nouvelle signature pour tous les rôles associés à ce Processus, avec le bouton "New Request"
|
||||
33 - En cliquant sur le bouton "New Request", une nouvelle fenêtre s'ouvre pour me permettre de rentrer la description, la visibilité, la date d'échéance, importer des documents, voir les signataires et "Request"
|
||||
34 - Dans le menu, je peux me déconnecter avec le bouton "Disconnect"
|
||||
|
||||
## TO DO
|
||||
## TO DO
|
||||
|
||||
### Documentation centralisée
|
||||
- Voir `/home/debian/4NK_env/docs/ihm_client/`
|
||||
|
||||
## Intégration dans l’environnement 4NK
|
||||
|
||||
`ihm_client` s’intègre dans l’architecture 4NK via une iframe et communique avec les services par variables d’environnement (préfixes `VITE_`/`NEXT_PUBLIC_`). Aucune URL de machine ne doit être codée en dur dans la documentation ou les exemples.
|
||||
|
||||
Références centrales:
|
||||
- `docs/DEEP_ARCHITECTURE_ANALYSIS.md`
|
||||
- `docs/TECHNICAL_REFERENCE.md`
|
||||
- `docs/DEPLOYMENT_GUIDE.md`
|
||||
## 📋 Fichiers centralisés
|
||||
|
||||
Les fichiers suivants sont centralisés dans le dépôt principal `4NK_env` :
|
||||
- `CODE_OF_CONDUCT.md` - Code de conduite
|
||||
- `CODEOWNERS` - Propriétaires du code
|
||||
- `CONTRIBUTING.md` - Guide de contribution
|
||||
- `LICENSE` - Licence du projet
|
||||
|
||||
Voir : [`4NK_env/CODE_OF_CONDUCT.md`](../../CODE_OF_CONDUCT.md), [`4NK_env/CODEOWNERS`](../../CODEOWNERS), [`4NK_env/CONTRIBUTING.md`](../../CONTRIBUTING.md), [`4NK_env/LICENSE`](../../LICENSE)
|
||||
|
||||
BIN
dist/assets/4nk_image.png
vendored
Executable file
|
After Width: | Height: | Size: 61 KiB |
BIN
dist/assets/4nk_revoke.jpg
vendored
Executable file
|
After Width: | Height: | Size: 67 KiB |
BIN
dist/assets/bgd.webp
vendored
Executable file
|
After Width: | Height: | Size: 509 KiB |
BIN
dist/assets/camera.jpg
vendored
Executable file
|
After Width: | Height: | Size: 73 KiB |
34
dist/assets/home.js
vendored
Executable file
@ -0,0 +1,34 @@
|
||||
document.querySelectorAll('.tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||||
document.getElementById(tab.getAttribute('data-tab')).classList.add('active');
|
||||
});
|
||||
});
|
||||
function toggleMenu() {
|
||||
var menu = document.getElementById('menu');
|
||||
if (menu.style.display === 'block') {
|
||||
menu.style.display = 'none';
|
||||
} else {
|
||||
menu.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
//// Modal
|
||||
function openModal() {
|
||||
document.getElementById('modal').style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modal').style.display = 'none';
|
||||
}
|
||||
|
||||
// Close modal when clicking outside of it
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('modal');
|
||||
if (event.target === modal) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
BIN
dist/assets/qr_code.png
vendored
Executable file
|
After Width: | Height: | Size: 8.9 KiB |
111
dist/authorized-client.html
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Redirection en cours…</title>
|
||||
<style>
|
||||
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 2rem; color: #0a0a0a; }
|
||||
.box { max-width: 720px; margin: 10vh auto; padding: 1.5rem; border: 1px solid #e5e7eb; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
|
||||
.muted { color: #6b7280; font-size: .95rem; }
|
||||
.error { color: #b91c1c; }
|
||||
.ok { color: #065f46; }
|
||||
code { background: #f3f4f6; padding: .2rem .35rem; border-radius: 6px; }
|
||||
a { color: #006BE0; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<h1>Connexion IdNot</h1>
|
||||
<p class="muted" id="status">Traitement du code d'autorisation…</p>
|
||||
<pre class="muted" id="details" style="white-space: pre-wrap"></pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function getQueryParam(name) {
|
||||
const params = new URLSearchParams(location.search);
|
||||
return params.get(name);
|
||||
}
|
||||
|
||||
function setCookie(name, value, days) {
|
||||
const expires = new Date(Date.now() + days * 864e5).toUTCString();
|
||||
// Domaine implicite: dev4.4nkweb.com (hébergement de cette page)
|
||||
document.cookie = name + '=' + encodeURIComponent(value) + '; Path=/; Expires=' + expires + '; SameSite=None; Secure';
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const code = getQueryParam('code');
|
||||
const statusEl = document.getElementById('status');
|
||||
const detailsEl = document.getElementById('details');
|
||||
|
||||
if (!code) {
|
||||
statusEl.textContent = 'Aucun code reçu dans la redirection IdNot.';
|
||||
statusEl.className = 'error';
|
||||
detailsEl.textContent = 'Paramètre attendu: ?code=…\nRetour à l\'espace: https://dev4.4nkweb.com/lecoffre/';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch('https://dev4.4nkweb.com/api/v1/idnot/auth', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': 'bridge_' + Math.random().toString(36).slice(2)
|
||||
},
|
||||
body: JSON.stringify({ code })
|
||||
});
|
||||
|
||||
const text = await resp.text();
|
||||
let data;
|
||||
try { data = JSON.parse(text); } catch (_) { data = null; }
|
||||
|
||||
if (!resp.ok) {
|
||||
statusEl.textContent = 'Connexion refusée (' + resp.status + ').';
|
||||
statusEl.className = 'error';
|
||||
detailsEl.textContent = (data && data.error && data.error.message) ? data.error.message : text;
|
||||
// Redirige néanmoins vers le front avec état d\'erreur afin d\'afficher un message utilisateur.
|
||||
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=' + encodeURIComponent(String(resp.status))); }, 600);
|
||||
return;
|
||||
}
|
||||
|
||||
// Attendu: { idNotUser, authToken }
|
||||
if (!data || !data.authToken) {
|
||||
statusEl.textContent = 'Réponse invalide du serveur.';
|
||||
statusEl.className = 'error';
|
||||
detailsEl.textContent = text;
|
||||
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=invalid_response'); }, 800);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stocker le jeton pour le domaine dev4 (utilisé par le front)
|
||||
setCookie('leCoffreAccessToken', data.authToken, 1);
|
||||
|
||||
statusEl.textContent = 'Connexion réussie, redirection…';
|
||||
statusEl.className = 'ok';
|
||||
detailsEl.textContent = '';
|
||||
|
||||
// Redirection vers le front avec token en hash en sauvegarde
|
||||
location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#token=' + encodeURIComponent(data.authToken));
|
||||
} catch (e) {
|
||||
statusEl.textContent = 'Erreur réseau lors de la connexion.';
|
||||
statusEl.className = 'error';
|
||||
detailsEl.textContent = String(e && e.message || e);
|
||||
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=network'); }, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
9139
dist/index.js
vendored
Normal file
1885
dist/sdk_client-6DvC0K4R.mjs
vendored
Normal file
877
dist/style/4nk.css
vendored
Executable file
@ -0,0 +1,877 @@
|
||||
:root {
|
||||
--primary-color
|
||||
: #3A506B;
|
||||
/* Bleu métallique */
|
||||
--secondary-color
|
||||
: #B0BEC5;
|
||||
/* Gris acier */
|
||||
--accent-color
|
||||
: #D68C45;
|
||||
/* Cuivre */
|
||||
}
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f4f4;
|
||||
background-image: url(../assets/bgd.webp);
|
||||
background-repeat:no-repeat;
|
||||
background-size: cover;
|
||||
background-blend-mode :soft-light;
|
||||
height: 100vh;
|
||||
}
|
||||
.message {
|
||||
margin: 30px 0;
|
||||
font-size: 14px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.message strong{
|
||||
font-family: "Segoe UI Emoji", "Noto Color Emoji", "Apple Color Emoji", sans-serif;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/** Modal Css */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 55%;
|
||||
height: 30%;
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin: 0;
|
||||
padding-bottom: 8px;
|
||||
width: 100%;
|
||||
font-size: 0.9em;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.confirmation-box {
|
||||
/* margin-top: 20px; */
|
||||
align-content: center;
|
||||
width: 70%;
|
||||
height: 20%;
|
||||
/* padding: 20px; */
|
||||
font-size: 1.5em;
|
||||
color: #333333;
|
||||
top: 5%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Confirmation Modal Styles */
|
||||
#confirmation-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.modal-confirmation {
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.modal-confirmation h3 {
|
||||
margin-bottom: 15px;
|
||||
color: var(--primary-color);
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.modal-confirmation p {
|
||||
margin: 8px 0;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.modal-footer button {
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--secondary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media only screen and (max-width: 600px) {
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
margin: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.modal-confirmation h3 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.modal-confirmation p {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-wrapper {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
background: radial-gradient(circle, white, var(--primary-color));
|
||||
/* background-color: #CFD8DC; */
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
color: #37474F;
|
||||
height: 9vh;
|
||||
width: 100vw;
|
||||
left: 0;
|
||||
top: 0;
|
||||
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, .2), 0px 16px 24px 2px rgba(0, 0, 0, .14), 0px 6px 30px 5px rgba(0, 0, 0, .12);
|
||||
|
||||
.nav-right-icons {
|
||||
display: flex;
|
||||
.notification-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.notification-bell, .burger-menu {
|
||||
z-index: 3;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.notification-badge {
|
||||
position: absolute;
|
||||
top: -.7rem;
|
||||
left: -.8rem;
|
||||
background-color: red;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
padding: 2.5px 6px;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.notification-board {
|
||||
position: absolute;
|
||||
width: 20rem;
|
||||
min-height: 8rem;
|
||||
background-color: white;
|
||||
right: 0.5rem;
|
||||
display: none;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
display: none;
|
||||
|
||||
.notification-element {
|
||||
padding: .8rem 0;
|
||||
width: 100%;
|
||||
&:hover {
|
||||
background-color: rgba(26, 28, 24, .08);
|
||||
}
|
||||
}
|
||||
.notification-element:not(:last-child) {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
height: 100%;
|
||||
width: 100vw;
|
||||
align-content: center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
display: grid;
|
||||
height: 100vh;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 10px;
|
||||
grid-auto-rows: 10vh 15vh 1fr;
|
||||
}
|
||||
.title-container {
|
||||
grid-column: 2 / 7;
|
||||
grid-row: 2;
|
||||
}
|
||||
.page-container {
|
||||
grid-column: 2 / 7;
|
||||
grid-row: 3 ;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
max-height: 60vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 20px 0;
|
||||
}
|
||||
@media only screen and (min-width: 600px) {
|
||||
.tab-container {
|
||||
display: none;
|
||||
}
|
||||
.page-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.process-container {
|
||||
grid-column: 3 / 6;
|
||||
grid-row: 3 ;
|
||||
|
||||
.card {
|
||||
min-width: 40vw;
|
||||
}
|
||||
}
|
||||
.separator {
|
||||
width: 2px;
|
||||
background-color: #78909C;
|
||||
height: 80%;
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
.tab-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
height: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.process-container {
|
||||
grid-column: 2 / 7;
|
||||
grid-row: 3 ;
|
||||
}
|
||||
.container {
|
||||
grid-auto-rows: 10vh 15vh 15vh 1fr;
|
||||
}
|
||||
.tab-container {
|
||||
grid-column: 1 / 8;
|
||||
grid-row: 3;
|
||||
}
|
||||
.page-container {
|
||||
grid-column: 2 / 7;
|
||||
grid-row: 4 ;
|
||||
}
|
||||
.separator {
|
||||
display: none;
|
||||
}
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #E0E4D6;
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
color: #6200ea;
|
||||
&:hover {
|
||||
background-color: rgba(26, 28, 24, .08);
|
||||
}
|
||||
}
|
||||
.tab.active {
|
||||
border-bottom: 2px solid #6200ea;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 80%;
|
||||
}
|
||||
.modal-content {
|
||||
width: 80%;
|
||||
height: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.emoji-display {
|
||||
font-family: "Segoe UI Emoji", "Noto Color Emoji", "Apple Color Emoji", sans-serif;
|
||||
font-size: 20px;
|
||||
|
||||
}
|
||||
|
||||
#emoji-display-2{
|
||||
margin-top: 30px;
|
||||
font-family: "Segoe UI Emoji", "Noto Color Emoji", "Apple Color Emoji", sans-serif;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#okButton{
|
||||
margin-bottom: 2em;
|
||||
cursor: pointer;
|
||||
background-color: #D0D0D7;
|
||||
color: white;
|
||||
border-style: none;
|
||||
border-radius: 5px;
|
||||
color: #000;
|
||||
padding: 2px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.pairing-request {
|
||||
font-family: "Segoe UI Emoji", "Noto Color Emoji", "Apple Color Emoji", sans-serif;
|
||||
font-size: 14px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.sp-address-btn {
|
||||
margin-bottom: 2em;
|
||||
cursor: pointer;
|
||||
background-color: #D0D0D7;
|
||||
color: white;
|
||||
border-style: none;
|
||||
border-radius: 5px;
|
||||
color: #000;
|
||||
padding: 2px;
|
||||
|
||||
}
|
||||
|
||||
.camera-card {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
/* height: 200px; */
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #3700b3;
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
min-width: 300px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
height: 60vh;
|
||||
justify-content: flex-start;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
font-size: .8em;
|
||||
position: relative;
|
||||
left: 2vw;
|
||||
width: 90%;
|
||||
.process-title {
|
||||
font-weight: bold;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
.process-element {
|
||||
padding: .4rem 0;
|
||||
&:hover {
|
||||
background-color: rgba(26, 28, 24, .08);
|
||||
}
|
||||
&.selected {
|
||||
background-color: rgba(26, 28, 24, .08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-description {
|
||||
padding: 20px;
|
||||
font-size: 1em;
|
||||
color: #333;
|
||||
width: 90%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
|
||||
.card-action {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 3.4rem;
|
||||
right: 1rem;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-content a {
|
||||
display: block;
|
||||
padding: 10px 20px;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
&:hover {
|
||||
background-color: rgba(26, 28, 24, .08);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-content a:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.qr-code-scanner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* QR READER */
|
||||
#qr-reader div {
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
#qr-reader div img{
|
||||
top: 15px ;
|
||||
right: 25px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
|
||||
/* INPUT CSS **/
|
||||
.input-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background-color: #ECEFF1;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 36vw;
|
||||
padding: 10px 0;
|
||||
font-size: 1em;
|
||||
border: none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
border-bottom: 2px solid #6200ea;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
position: absolute;
|
||||
margin-top: -0.5em;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 10px 0;
|
||||
font-size: 1em;
|
||||
color: #999;
|
||||
pointer-events: none;
|
||||
transition: transform 0.3s, color 0.3s, font-size 0.3s;
|
||||
}
|
||||
|
||||
.input-field:focus + .input-label,
|
||||
.input-field:not(:placeholder-shown) + .input-label {
|
||||
transform: translateY(-20px);
|
||||
font-size: 0.8em;
|
||||
color: #6200ea;
|
||||
}
|
||||
|
||||
.input-underline {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background-color: #6200ea;
|
||||
transition: width 0.3s, left 0.3s;
|
||||
}
|
||||
|
||||
.input-field:focus ~ .input-underline {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
position: absolute;
|
||||
flex-direction: column;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
display: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-content span {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.dropdown-content span:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** AUTOCOMPLETE **/
|
||||
|
||||
select[data-multi-select-plugin] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.multi-select-component {
|
||||
width: 36vw;
|
||||
padding: 5px 0;
|
||||
font-size: 1em;
|
||||
border: none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
-o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||
}
|
||||
|
||||
.autocomplete-list {
|
||||
border-radius: 4px 0px 0px 4px;
|
||||
}
|
||||
|
||||
.multi-select-component:focus-within {
|
||||
box-shadow: inset 0px 0px 0px 2px #78ABFE;
|
||||
}
|
||||
|
||||
.multi-select-component .btn-group {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.multiselect-native-select .multiselect-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.selected-processes {
|
||||
background-color: white;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
.selected-wrapper {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
border: 1px solid #d9d9d9;
|
||||
background-color: #ededed;
|
||||
white-space: nowrap;
|
||||
margin: 1px 5px 5px 0;
|
||||
height: 22px;
|
||||
vertical-align: top;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.selected-wrapper .selected-label {
|
||||
max-width: 514px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-left: 4px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.selected-wrapper .selected-close {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
line-height: 1.49em;
|
||||
margin-left: 5px;
|
||||
padding-bottom: 10px;
|
||||
height: 100%;
|
||||
vertical-align: top;
|
||||
padding-right: 4px;
|
||||
opacity: 0.2;
|
||||
color: #000;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.search-container .selected-input {
|
||||
background: none;
|
||||
border: 0;
|
||||
height: 20px;
|
||||
width: 60px;
|
||||
padding: 0;
|
||||
margin-bottom: 6px;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.search-container .selected-input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dropdown-icon.active {
|
||||
transform: rotateX(180deg)
|
||||
}
|
||||
|
||||
.search-container .dropdown-icon {
|
||||
display: inline-block;
|
||||
padding: 10px 5px;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 0 !important;
|
||||
/* needed */
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
/* SVG background image */
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2212%22%20height%3D%2212%22%20viewBox%3D%220%200%2012%2012%22%3E%3Ctitle%3Edown-arrow%3C%2Ftitle%3E%3Cg%20fill%3D%22%23818181%22%3E%3Cpath%20d%3D%22M10.293%2C3.293%2C6%2C7.586%2C1.707%2C3.293A1%2C1%2C0%2C0%2C0%2C.293%2C4.707l5%2C5a1%2C1%2C0%2C0%2C0%2C1.414%2C0l5-5a1%2C1%2C0%2C1%2C0-1.414-1.414Z%22%20fill%3D%22%23818181%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3C%2Fsvg%3E");
|
||||
background-position: center;
|
||||
background-size: 10px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.search-container ul {
|
||||
position: absolute;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
z-index: 3;
|
||||
margin-top: 29px;
|
||||
width: 100%;
|
||||
right: 0px;
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||
}
|
||||
|
||||
.search-container ul :focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-container ul li {
|
||||
display: block;
|
||||
text-align: left;
|
||||
padding: 8px 29px 2px 12px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-size: 14px;
|
||||
min-height: 31px;
|
||||
}
|
||||
|
||||
.search-container ul li:first-child {
|
||||
border-top: 1px solid #ccc;
|
||||
border-radius: 4px 0px 0 0;
|
||||
}
|
||||
|
||||
.search-container ul li:last-child {
|
||||
border-radius: 4px 0px 0 0;
|
||||
}
|
||||
|
||||
|
||||
.search-container ul li:hover.not-cursor {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.search-container ul li:hover {
|
||||
color: #333;
|
||||
background-color: #f0f0f0;
|
||||
;
|
||||
border-color: #adadad;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Adding scrool to select options */
|
||||
.autocomplete-list {
|
||||
max-height: 130px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**************************************** Process page card ******************************************************/
|
||||
.process-card {
|
||||
min-width: 300px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
min-height: 40vh;
|
||||
max-height: 60vh;
|
||||
justify-content: space-between;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
|
||||
}
|
||||
|
||||
.process-card-content {
|
||||
text-align: left;
|
||||
font-size: .8em;
|
||||
position: relative;
|
||||
left: 2vw;
|
||||
width: 90%;
|
||||
.process-title {
|
||||
font-weight: bold;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
.process-element {
|
||||
padding: .4rem 0;
|
||||
&:hover {
|
||||
background-color: rgba(26, 28, 24, .08);
|
||||
}
|
||||
&.selected {
|
||||
background-color: rgba(26, 28, 24, .08);
|
||||
}
|
||||
}
|
||||
.selected-process-zone {
|
||||
background-color: rgba(26, 28, 24, .08);
|
||||
}
|
||||
}
|
||||
|
||||
.process-card-description {
|
||||
padding: 20px;
|
||||
font-size: 1em;
|
||||
color: #333;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
|
||||
.process-card-action {
|
||||
width: 100%;
|
||||
}
|
||||
1507
dist/style/account.css
vendored
Executable file
597
dist/style/chat.css
vendored
Executable file
@ -0,0 +1,597 @@
|
||||
/* Styles de base */
|
||||
:root {
|
||||
--primary-color: #3A506B;
|
||||
/* Bleu métallique */
|
||||
--secondary-color: #B0BEC5;
|
||||
/* Gris acier */
|
||||
--accent-color: #D68C45;
|
||||
/* Cuivre */
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/* 4NK NAVBAR */
|
||||
|
||||
.brand-logo {
|
||||
text-align: center;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-wrapper {
|
||||
position: fixed;
|
||||
background: radial-gradient(circle, white, var(--primary-color));
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: #37474F;
|
||||
height: 9vh;
|
||||
width: 100vw;
|
||||
left: 0;
|
||||
top: 0;
|
||||
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, .2), 0px 16px 24px 2px rgba(0, 0, 0, .14), 0px 6px 30px 5px rgba(0, 0, 0, .12);
|
||||
}
|
||||
|
||||
/* Icônes de la barre de navigation */
|
||||
.nav-right-icons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.notification-bell,
|
||||
.burger-menu {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notification-container {
|
||||
position: relative;
|
||||
/* Conserve la position pour le notification-board */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.notification-board {
|
||||
position: absolute;
|
||||
/* Position absolue pour le placer par rapport au container */
|
||||
top: 40px;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
width: 200px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
/* Scroll si les notifications dépassent la taille */
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
/* Définit la priorité d'affichage au-dessus des autres éléments */
|
||||
display: none;
|
||||
/* Par défaut, la notification est masquée */
|
||||
}
|
||||
|
||||
.notification-item{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notification-badge {
|
||||
position: absolute;
|
||||
top: -18px;
|
||||
right: 35px;
|
||||
background-color: red;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
display: none;
|
||||
/* S'affiche seulement lorsqu'il y a des notifications */
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Par défaut, le menu est masqué */
|
||||
#menu {
|
||||
display: none;
|
||||
/* Menu caché par défaut */
|
||||
transition: display 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.burger-menu {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Icône burger */
|
||||
#burger-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 3.4rem;
|
||||
right: 1rem;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-content a {
|
||||
display: block;
|
||||
padding: 10px 20px;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(26, 28, 24, .08);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-content a:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Ajustement pour la barre de navigation fixe */
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 90vh;
|
||||
margin-top: 9vh;
|
||||
margin-left: -1%;
|
||||
text-align: left;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
|
||||
/* Liste des groupes */
|
||||
|
||||
.group-list {
|
||||
width: 25%;
|
||||
background-color: #1f2c3d;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
border-right: 2px solid #2c3e50;
|
||||
flex-shrink: 0;
|
||||
padding-right: 10px;
|
||||
height: 91vh;
|
||||
}
|
||||
.group-list ul {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
padding-right: 10px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.group-list li {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
background-color: #273646;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.group-list li:hover {
|
||||
background-color: #34495e;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
|
||||
.group-list .member-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.group-list .member-container button {
|
||||
margin-left: 40px;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border: 0px solid var(--primary-color);
|
||||
border-radius: 50px;
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
right: -25px;
|
||||
}
|
||||
|
||||
.group-list .member-container button:hover {
|
||||
background: var(--accent-color)
|
||||
}
|
||||
|
||||
|
||||
/* Zone de chat */
|
||||
.chat-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background-color:#f1f1f1;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
margin: 1% 0% 0.5% 1%;
|
||||
}
|
||||
|
||||
/* En-tête du chat */
|
||||
.chat-header {
|
||||
background-color: #34495e;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
border-radius: 10px 10px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
.messages {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
background-color: #f1f1f1;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
display: flex;
|
||||
margin: 8px;
|
||||
}
|
||||
.message-container .message {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.message-container .message.user {
|
||||
align-self: flex-end;
|
||||
margin-left: auto;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 70%;
|
||||
padding: 10px;
|
||||
border-radius: 12px;
|
||||
background:var(--secondary-color);
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
/* Messages de l'utilisateur */
|
||||
.message.user {
|
||||
background: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 0.7em;
|
||||
opacity: 0.7;
|
||||
margin-left: 0px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
|
||||
/* Amélioration de l'esthétique des messages */
|
||||
/* .message.user:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: -10px;
|
||||
border: 10px solid transparent;
|
||||
border-left-color: #3498db;
|
||||
} */
|
||||
|
||||
/* Zone de saisie */
|
||||
.input-area {
|
||||
padding: 10px;
|
||||
background-color: #bdc3c7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 10px;
|
||||
margin: 1%;
|
||||
/* Alignement vertical */
|
||||
}
|
||||
|
||||
.input-area input[type="text"] {
|
||||
flex: 1;
|
||||
/* Prend l'espace restant */
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.input-area .attachment-icon {
|
||||
margin: 0 10px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-area button {
|
||||
padding: 10px;
|
||||
margin-left: 10px;
|
||||
background-color: #2980b9;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-area button:hover {
|
||||
background-color: #1f608d;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin: 20px 0px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tabs button {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border: 0px solid var(--primary-color);
|
||||
margin-right: 5px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.tabs button:hover {
|
||||
background: var(--secondary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Signature */
|
||||
.signature-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background-color:#f1f1f1;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
margin: 1% 0% 0.5% 1%;
|
||||
transition: all 1s ease 0.1s;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.signature-area.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.signature-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border-radius: 10px 10px 0 0;
|
||||
padding-left: 4%;
|
||||
}
|
||||
|
||||
.signature-content {
|
||||
padding: 10px;
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--primary-color);
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
margin: 1%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.signature-description {
|
||||
height: 20%;
|
||||
width: 100%;
|
||||
margin: 0% 10% 0% 10%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.signature-description li {
|
||||
margin: 1% 0% 1% 0%;
|
||||
list-style: none;
|
||||
padding: 2%;
|
||||
border-radius: 10px;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--secondary-color);
|
||||
width: 20%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
margin-right: 2%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.signature-description li .member-list {
|
||||
margin-left: -30%;
|
||||
}
|
||||
|
||||
.signature-description li .member-list li {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.signature-description li .member-list li:hover {
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.signature-documents {
|
||||
height: 80%;
|
||||
width: 100%;
|
||||
margin: 0% 10% 0% 10%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.signature-documents-header {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 15%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#request-document-button {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
margin-left: 5%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#request-document-button:hover {
|
||||
background-color: var(--accent-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#close-signature {
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
margin-right: 2%;
|
||||
border-radius: 50%;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: -3%;
|
||||
margin-top: -5%;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#close-signature:hover {
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* REQUEST MODAL */
|
||||
.request-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--secondary-color);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 1.5em;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close-modal:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.modal-members {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.modal-members ul li{
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.file-upload-container {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
margin: 5px 0;
|
||||
background: var(--background-color-secondary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.remove-file {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.remove-file:hover {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
#message-input {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
resize: none;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
max-width: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media screen and (max-width: 768px) {
|
||||
.group-list {
|
||||
display: none;
|
||||
/* Masquer la liste des groupes sur les petits écrans */
|
||||
}
|
||||
|
||||
.chat-area {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--primary-color);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--secondary-color);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent-color);
|
||||
}
|
||||
1664
dist/style/signature.css
vendored
Executable file
2
dist/types/components/header/header.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export declare function unpair(): Promise<void>;
|
||||
export declare function initHeader(): Promise<void>;
|
||||
1
dist/types/components/modal/confirmation-modal.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export declare function closeConfirmationModal(): Promise<void>;
|
||||
10
dist/types/components/qrcode-scanner/qrcode-scanner-component.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
export default class QrScannerComponent extends HTMLElement {
|
||||
videoElement: any;
|
||||
wrapper: any;
|
||||
qrScanner: any;
|
||||
constructor();
|
||||
connectedCallback(): void;
|
||||
initializeScanner(): Promise<void>;
|
||||
onQrCodeScanned(result: any): Promise<void>;
|
||||
disconnectedCallback(): void;
|
||||
}
|
||||
1
dist/types/components/validation-modal/validation-modal.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export declare function initValidationModal(processDiffs: any): Promise<void>;
|
||||
14
dist/types/components/validation-rule-modal/validation-rule-modal.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
export interface ValidationRule {
|
||||
quorum: number;
|
||||
fields: string[];
|
||||
min_sig_member: number;
|
||||
}
|
||||
/**
|
||||
* Loads and injects the modal HTML into the document if not already loaded.
|
||||
*/
|
||||
export declare function loadValidationRuleModal(templatePath?: string): Promise<void>;
|
||||
/**
|
||||
* Opens the modal and lets the user input a ValidationRule.
|
||||
* Calls the callback with the constructed rule on submit.
|
||||
*/
|
||||
export declare function showValidationRuleModal(onSubmit: (rule: ValidationRule) => void): void;
|
||||
3
dist/types/index.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as Services } from './services/service';
|
||||
export { default as Database } from './services/database.service';
|
||||
export { MessageType } from './models/process.model';
|
||||
24
dist/types/interface/groupInterface.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
import { DocumentSignature } from '~/models/signature.models';
|
||||
export interface Group {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
roles: Array<{
|
||||
name: string;
|
||||
members: Array<{
|
||||
id: string | number;
|
||||
name: string;
|
||||
}>;
|
||||
documents?: Array<any>;
|
||||
}>;
|
||||
commonDocuments: Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
visibility: string;
|
||||
description: string;
|
||||
createdAt?: string | null;
|
||||
deadline?: string | null;
|
||||
signatures?: DocumentSignature[];
|
||||
status?: string;
|
||||
}>;
|
||||
}
|
||||
10
dist/types/interface/memberInterface.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
export interface Member {
|
||||
id: string | number;
|
||||
name: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
processRoles?: Array<{
|
||||
processId: number | string;
|
||||
role: string;
|
||||
}>;
|
||||
}
|
||||
13
dist/types/main.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
import { SignatureComponent } from './pages/signature/signature-component';
|
||||
import { SignatureElement } from './pages/signature/signature';
|
||||
import { AccountComponent } from './pages/account/account-component';
|
||||
import { AccountElement } from './pages/account/account';
|
||||
export { SignatureComponent, SignatureElement, AccountComponent, AccountElement };
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'signature-component': SignatureComponent;
|
||||
'signature-element': SignatureElement;
|
||||
'account-component': AccountComponent;
|
||||
'account-element': AccountElement;
|
||||
}
|
||||
}
|
||||
118
dist/types/mocks/mock-account/constAccountMock.d.ts
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
export declare const ALLOWED_ROLES: string[];
|
||||
export declare const STORAGE_KEYS: {
|
||||
pairing: string;
|
||||
wallet: string;
|
||||
process: string;
|
||||
data: string;
|
||||
};
|
||||
export declare const defaultRows: {
|
||||
column1: string;
|
||||
column2: string;
|
||||
column3: string;
|
||||
}[];
|
||||
export declare const mockNotifications: {
|
||||
[key: string]: Notification[];
|
||||
};
|
||||
export declare const notificationMessages: string[];
|
||||
export declare const mockDataRows: {
|
||||
column1: string;
|
||||
column2: string;
|
||||
column3: string;
|
||||
column4: string;
|
||||
column5: string;
|
||||
column6: string;
|
||||
processName: string;
|
||||
zone: string;
|
||||
}[];
|
||||
export declare const mockProcessRows: ({
|
||||
process: string;
|
||||
role: string;
|
||||
notification: {
|
||||
messages: {
|
||||
id: number;
|
||||
read: boolean;
|
||||
date: string;
|
||||
message: string;
|
||||
}[];
|
||||
unread?: undefined;
|
||||
total?: undefined;
|
||||
};
|
||||
} | {
|
||||
process: string;
|
||||
role: string;
|
||||
notification: {
|
||||
unread: number;
|
||||
total: number;
|
||||
messages: {
|
||||
id: number;
|
||||
read: boolean;
|
||||
date: string;
|
||||
message: string;
|
||||
}[];
|
||||
};
|
||||
})[];
|
||||
export declare const mockContracts: {
|
||||
'Contract #123': {
|
||||
title: string;
|
||||
date: string;
|
||||
parties: string[];
|
||||
terms: string[];
|
||||
content: string;
|
||||
};
|
||||
'Contract #456': {
|
||||
title: string;
|
||||
date: string;
|
||||
parties: string[];
|
||||
terms: string[];
|
||||
content: string;
|
||||
};
|
||||
'Contract #789': {
|
||||
title: string;
|
||||
date: string;
|
||||
parties: string[];
|
||||
terms: string[];
|
||||
content: string;
|
||||
};
|
||||
'Contract #101': {
|
||||
title: string;
|
||||
date: string;
|
||||
parties: string[];
|
||||
terms: string[];
|
||||
content: string;
|
||||
};
|
||||
'Contract #102': {
|
||||
title: string;
|
||||
date: string;
|
||||
parties: string[];
|
||||
terms: string[];
|
||||
content: string;
|
||||
};
|
||||
'Contract #103': {
|
||||
title: string;
|
||||
date: string;
|
||||
parties: string[];
|
||||
terms: string[];
|
||||
content: string;
|
||||
};
|
||||
'Contract #104': {
|
||||
title: string;
|
||||
date: string;
|
||||
parties: string[];
|
||||
terms: string[];
|
||||
content: string;
|
||||
};
|
||||
'Contract #105': {
|
||||
title: string;
|
||||
date: string;
|
||||
parties: string[];
|
||||
terms: string[];
|
||||
content: string;
|
||||
};
|
||||
'Contract #106': {
|
||||
title: string;
|
||||
date: string;
|
||||
parties: string[];
|
||||
terms: string[];
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
38
dist/types/mocks/mock-account/interfacesAccountMock.d.ts
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
export interface Row {
|
||||
column1: string;
|
||||
column2: string;
|
||||
column3: string;
|
||||
}
|
||||
export interface Contract {
|
||||
title: string;
|
||||
date: string;
|
||||
parties: string[];
|
||||
terms: string[];
|
||||
content: string;
|
||||
}
|
||||
export interface WalletRow {
|
||||
column1: string;
|
||||
column2: string;
|
||||
column3: string;
|
||||
}
|
||||
export interface DataRow {
|
||||
column1: string;
|
||||
column2: string;
|
||||
column3: string;
|
||||
column4: string;
|
||||
column5: string;
|
||||
column6: string;
|
||||
processName: string;
|
||||
zone: string;
|
||||
}
|
||||
export interface Notification {
|
||||
message: string;
|
||||
timestamp: string;
|
||||
isRead: boolean;
|
||||
}
|
||||
export interface NotificationMessage {
|
||||
id: number;
|
||||
read: boolean;
|
||||
date: string;
|
||||
message: string;
|
||||
}
|
||||
119
dist/types/mocks/mock-signature/groupsMock.d.ts
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
export const groupsMock: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
commonDocuments: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
visibility: string;
|
||||
status: string;
|
||||
createdAt: null;
|
||||
deadline: null;
|
||||
signatures: never[];
|
||||
}[];
|
||||
roles: ({
|
||||
name: string;
|
||||
members: {
|
||||
id: number;
|
||||
name: string;
|
||||
}[];
|
||||
documents: ({
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
visibility: string;
|
||||
createdAt: string;
|
||||
deadline: string;
|
||||
signatures: ({
|
||||
member: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
signed: boolean;
|
||||
signedAt: string;
|
||||
} | {
|
||||
member: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
signed: boolean;
|
||||
signedAt?: undefined;
|
||||
})[];
|
||||
status?: undefined;
|
||||
} | {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
visibility: string;
|
||||
createdAt: null;
|
||||
deadline: null;
|
||||
signatures: never[];
|
||||
status?: undefined;
|
||||
} | {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
visibility: string;
|
||||
status: string;
|
||||
createdAt: null;
|
||||
deadline: null;
|
||||
signatures: never[];
|
||||
})[];
|
||||
} | {
|
||||
name: string;
|
||||
members: {
|
||||
id: number;
|
||||
name: string;
|
||||
}[];
|
||||
documents: ({
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
visibility: string;
|
||||
createdAt: string;
|
||||
deadline: string;
|
||||
signatures: {
|
||||
member: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
signed: boolean;
|
||||
signedAt: string;
|
||||
}[];
|
||||
status?: undefined;
|
||||
} | {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
visibility: string;
|
||||
status: string;
|
||||
createdAt: null;
|
||||
deadline: null;
|
||||
signatures: never[];
|
||||
} | {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
visibility: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
deadline: string;
|
||||
signatures: ({
|
||||
member: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
signed: boolean;
|
||||
signedAt: string;
|
||||
} | {
|
||||
member: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
signed: boolean;
|
||||
signedAt?: undefined;
|
||||
})[];
|
||||
})[];
|
||||
})[];
|
||||
}[];
|
||||
10
dist/types/mocks/mock-signature/membersMocks.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
export const membersMock: {
|
||||
id: number;
|
||||
name: string;
|
||||
avatar: string;
|
||||
email: string;
|
||||
processRoles: {
|
||||
processId: number;
|
||||
role: string;
|
||||
}[];
|
||||
}[];
|
||||
9
dist/types/mocks/mock-signature/messagesMock.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
export declare const messagesMock: {
|
||||
memberId: number;
|
||||
messages: {
|
||||
id: number;
|
||||
sender: string;
|
||||
text: string;
|
||||
time: string;
|
||||
}[];
|
||||
}[];
|
||||
24
dist/types/models/notification.model.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
export interface INotification {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
sendToNotificationPage?: boolean;
|
||||
path?: string;
|
||||
}
|
||||
export interface IUser {
|
||||
id: string;
|
||||
information?: any;
|
||||
}
|
||||
export interface IMessage {
|
||||
id: string;
|
||||
message: any;
|
||||
}
|
||||
export interface UserDiff {
|
||||
new_state_merkle_root: string;
|
||||
field: string;
|
||||
previous_value: string;
|
||||
new_value: string;
|
||||
notify_user: boolean;
|
||||
need_validation: boolean;
|
||||
proof: any;
|
||||
}
|
||||
56
dist/types/models/process.model.d.ts
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
export interface IProcess {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
icon?: string;
|
||||
zoneList: IZone[];
|
||||
}
|
||||
export interface IZone {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
icon?: string;
|
||||
}
|
||||
export interface INotification {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
sendToNotificationPage?: boolean;
|
||||
path?: string;
|
||||
}
|
||||
export declare enum MessageType {
|
||||
LISTENING = "LISTENING",
|
||||
REQUEST_LINK = "REQUEST_LINK",
|
||||
LINK_ACCEPTED = "LINK_ACCEPTED",
|
||||
CREATE_PAIRING = "CREATE_PAIRING",
|
||||
PAIRING_CREATED = "PAIRING_CREATED",
|
||||
ERROR = "ERROR",
|
||||
VALIDATE_TOKEN = "VALIDATE_TOKEN",
|
||||
RENEW_TOKEN = "RENEW_TOKEN",
|
||||
GET_PAIRING_ID = "GET_PAIRING_ID",
|
||||
GET_PROCESSES = "GET_PROCESSES",
|
||||
GET_MY_PROCESSES = "GET_MY_PROCESSES",
|
||||
PROCESSES_RETRIEVED = "PROCESSES_RETRIEVED",
|
||||
RETRIEVE_DATA = "RETRIEVE_DATA",
|
||||
DATA_RETRIEVED = "DATA_RETRIEVED",
|
||||
DECODE_PUBLIC_DATA = "DECODE_PUBLIC_DATA",
|
||||
PUBLIC_DATA_DECODED = "PUBLIC_DATA_DECODED",
|
||||
GET_MEMBER_ADDRESSES = "GET_MEMBER_ADDRESSES",
|
||||
MEMBER_ADDRESSES_RETRIEVED = "MEMBER_ADDRESSES_RETRIEVED",
|
||||
CREATE_PROCESS = "CREATE_PROCESS",
|
||||
PROCESS_CREATED = "PROCESS_CREATED",
|
||||
UPDATE_PROCESS = "UPDATE_PROCESS",
|
||||
PROCESS_UPDATED = "PROCESS_UPDATED",
|
||||
NOTIFY_UPDATE = "NOTIFY_UPDATE",
|
||||
UPDATE_NOTIFIED = "UPDATE_NOTIFIED",
|
||||
VALIDATE_STATE = "VALIDATE_STATE",
|
||||
STATE_VALIDATED = "STATE_VALIDATED",
|
||||
HASH_VALUE = "HASH_VALUE",
|
||||
VALUE_HASHED = "VALUE_HASHED",
|
||||
GET_MERKLE_PROOF = "GET_MERKLE_PROOF",
|
||||
MERKLE_PROOF_RETRIEVED = "MERKLE_PROOF_RETRIEVED",
|
||||
VALIDATE_MERKLE_PROOF = "VALIDATE_MERKLE_PROOF",
|
||||
MERKLE_PROOF_VALIDATED = "MERKLE_PROOF_VALIDATED",
|
||||
ADD_DEVICE = "ADD_DEVICE",
|
||||
DEVICE_ADDED = "DEVICE_ADDED"
|
||||
}
|
||||
60
dist/types/models/signature.models.d.ts
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
export interface Group {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
roles: {
|
||||
id?: number;
|
||||
name: string;
|
||||
members: {
|
||||
id: string | number;
|
||||
name: string;
|
||||
}[];
|
||||
documents?: {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
visibility: string;
|
||||
createdAt: string | null;
|
||||
deadline: string | null;
|
||||
signatures: DocumentSignature[];
|
||||
status?: string;
|
||||
files?: Array<{
|
||||
name: string;
|
||||
url: string;
|
||||
}>;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
export interface Message {
|
||||
id: number;
|
||||
sender: string;
|
||||
text?: string;
|
||||
time: string;
|
||||
type: 'text' | 'file';
|
||||
fileName?: string;
|
||||
fileData?: string;
|
||||
}
|
||||
export interface MemberMessages {
|
||||
memberId: string;
|
||||
messages: Message[];
|
||||
}
|
||||
export interface DocumentSignature {
|
||||
signed: boolean;
|
||||
member: {
|
||||
name: string;
|
||||
};
|
||||
signedAt?: string;
|
||||
}
|
||||
export interface RequestParams {
|
||||
processId: number;
|
||||
processName: string;
|
||||
roleId: number;
|
||||
roleName: string;
|
||||
documentId: number;
|
||||
documentName: string;
|
||||
}
|
||||
export interface Notification {
|
||||
memberId: string;
|
||||
text: string;
|
||||
time: string;
|
||||
}
|
||||
12
dist/types/pages/account/account-component.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import { AccountElement } from './account';
|
||||
declare class AccountComponent extends HTMLElement {
|
||||
_callback: any;
|
||||
accountElement: AccountElement | null;
|
||||
constructor();
|
||||
connectedCallback(): void;
|
||||
fetchData(): Promise<void>;
|
||||
set callback(fn: any);
|
||||
get callback(): any;
|
||||
render(): void;
|
||||
}
|
||||
export { AccountComponent };
|
||||
98
dist/types/pages/account/account.d.ts
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
initAccount: () => void;
|
||||
showContractPopup: (contractId: string) => void;
|
||||
showPairing: () => Promise<void>;
|
||||
showWallet: () => void;
|
||||
showData: () => void;
|
||||
addWalletRow: () => void;
|
||||
confirmWalletRow: () => void;
|
||||
cancelWalletRow: () => void;
|
||||
openAvatarPopup: () => void;
|
||||
closeAvatarPopup: () => void;
|
||||
editDeviceName: (cell: HTMLTableCellElement) => void;
|
||||
showNotifications: (processName: string) => void;
|
||||
closeNotificationPopup: (event: Event) => void;
|
||||
markAsRead: (processName: string, messageId: number, element: HTMLElement) => void;
|
||||
exportRecovery: () => void;
|
||||
confirmDeleteAccount: () => void;
|
||||
deleteAccount: () => void;
|
||||
updateNavbarBanner: (bannerUrl: string) => void;
|
||||
saveBannerToLocalStorage: (bannerUrl: string) => void;
|
||||
loadSavedBanner: () => void;
|
||||
cancelAddRowPairing: () => void;
|
||||
saveName: (cell: HTMLElement, input: HTMLInputElement) => void;
|
||||
showProcessNotifications: (processName: string) => void;
|
||||
handleLogout: () => void;
|
||||
initializeEventListeners: () => void;
|
||||
showProcess: () => void;
|
||||
showProcessCreation: () => void;
|
||||
showDocumentValidation: () => void;
|
||||
updateNavbarName: (name: string) => void;
|
||||
updateNavbarLastName: (lastName: string) => void;
|
||||
showAlert: (title: string, text?: string, icon?: string) => void;
|
||||
addRowPairing: () => void;
|
||||
confirmRowPairing: () => void;
|
||||
cancelRowPairing: () => void;
|
||||
deleteRowPairing: (button: HTMLButtonElement) => void;
|
||||
generateRecoveryWords: () => string[];
|
||||
exportUserData: () => void;
|
||||
updateActionButtons: () => void;
|
||||
showQRCodeModal: (pairingId: string) => void;
|
||||
}
|
||||
}
|
||||
declare class AccountElement extends HTMLElement {
|
||||
private dom;
|
||||
constructor();
|
||||
connectedCallback(): void;
|
||||
private showAlert;
|
||||
private confirmDeleteAccount;
|
||||
private deleteAccount;
|
||||
private updateNavbarBanner;
|
||||
private saveBannerToLocalStorage;
|
||||
private loadSavedBanner;
|
||||
private closeNotificationPopup;
|
||||
private markAsRead;
|
||||
private calculateNotifications;
|
||||
private exportRecovery;
|
||||
private generateRecoveryWords;
|
||||
private exportUserData;
|
||||
private updateActionButtons;
|
||||
private getConfirmFunction;
|
||||
private getCancelFunction;
|
||||
private addRowPairing;
|
||||
private updateTableContent;
|
||||
private confirmRowPairing;
|
||||
private cancelRowPairing;
|
||||
private resetButtonContainer;
|
||||
private deleteRowPairing;
|
||||
private editDeviceName;
|
||||
private finishEditing;
|
||||
private handleAvatarUpload;
|
||||
private showProcessCreation;
|
||||
private showDocumentValidation;
|
||||
private showProcess;
|
||||
private showProcessNotifications;
|
||||
private handleLogout;
|
||||
private showContractPopup;
|
||||
private hideAllContent;
|
||||
private showPairing;
|
||||
private showWallet;
|
||||
private updateWalletTableContent;
|
||||
private showData;
|
||||
private addWalletRow;
|
||||
private confirmWalletRow;
|
||||
private cancelWalletRow;
|
||||
private updateDataTableContent;
|
||||
private openAvatarPopup;
|
||||
private setupEventListeners;
|
||||
private closeAvatarPopup;
|
||||
private loadAvatar;
|
||||
private loadUserInfo;
|
||||
private updateNavbarName;
|
||||
private updateNavbarLastName;
|
||||
private updateProfilePreview;
|
||||
private initializeEventListeners;
|
||||
private showQRCodeModal;
|
||||
}
|
||||
export { AccountElement };
|
||||
33
dist/types/pages/account/document-validation.d.ts
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
export interface Vin {
|
||||
txid: string;
|
||||
vout: number;
|
||||
prevout: {
|
||||
scriptpubkey: string;
|
||||
scriptpubkey_asm: string;
|
||||
scriptpubkey_type: string;
|
||||
scriptpubkey_address: string;
|
||||
value: number;
|
||||
};
|
||||
scriptsig: string;
|
||||
scriptsig_asm: string;
|
||||
witness: string[];
|
||||
is_coinbase: boolean;
|
||||
sequence: number;
|
||||
}
|
||||
export interface TransactionInfo {
|
||||
txid: string;
|
||||
version: number;
|
||||
locktime: number;
|
||||
vin: Vin[];
|
||||
vout: any[];
|
||||
size: number;
|
||||
weight: number;
|
||||
fee: number;
|
||||
status: {
|
||||
confirmed: boolean;
|
||||
block_height: number;
|
||||
block_hash: string;
|
||||
block_time: number;
|
||||
};
|
||||
}
|
||||
export declare function getDocumentValidation(container: HTMLElement): void;
|
||||
8
dist/types/pages/account/key-value-section.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import { RoleDefinition } from '../../../pkg/sdk_client.js';
|
||||
export declare function createKeyValueSection(title: string, id: string, isRoleSection?: boolean): {
|
||||
element: HTMLDivElement;
|
||||
getData: () => Record<string, RoleDefinition> | Record<string, string | {
|
||||
type: string;
|
||||
data: Uint8Array;
|
||||
}>;
|
||||
};
|
||||
1
dist/types/pages/account/process-creation.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export declare function getProcessCreation(container: HTMLElement): Promise<void>;
|
||||
4
dist/types/pages/account/process.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
export declare function createProcessTab(container: HTMLElement, processes: {
|
||||
name: string;
|
||||
publicData: Record<string, any>;
|
||||
}[]): HTMLElement;
|
||||
8
dist/types/pages/home/home-component.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
export declare class LoginComponent extends HTMLElement {
|
||||
_callback: any;
|
||||
constructor();
|
||||
connectedCallback(): void;
|
||||
set callback(fn: any);
|
||||
get callback(): any;
|
||||
render(): void;
|
||||
}
|
||||
4
dist/types/pages/home/home.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import QrScannerComponent from '../../components/qrcode-scanner/qrcode-scanner-component';
|
||||
export { QrScannerComponent };
|
||||
export declare function initHomePage(): Promise<void>;
|
||||
export declare function openModal(myAddress: string, receiverAddress: string): Promise<void>;
|
||||
10
dist/types/pages/process-element/process-component.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
export declare class ProcessListComponent extends HTMLElement {
|
||||
_callback: any;
|
||||
id: string;
|
||||
zone: string;
|
||||
constructor();
|
||||
connectedCallback(): void;
|
||||
set callback(fn: any);
|
||||
get callback(): any;
|
||||
render(): void;
|
||||
}
|
||||
1
dist/types/pages/process-element/process-element.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export declare function initProcessElement(id: string, zone: string): Promise<void>;
|
||||
12
dist/types/pages/signature/signature-component.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import { SignatureElement } from './signature';
|
||||
declare class SignatureComponent extends HTMLElement {
|
||||
_callback: any;
|
||||
signatureElement: SignatureElement | null;
|
||||
constructor();
|
||||
connectedCallback(): void;
|
||||
fetchData(): Promise<void>;
|
||||
set callback(fn: any);
|
||||
get callback(): any;
|
||||
render(): void;
|
||||
}
|
||||
export { SignatureComponent };
|
||||
71
dist/types/pages/signature/signature.d.ts
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
toggleUserList: () => void;
|
||||
switchUser: (userId: string | number) => void;
|
||||
closeProcessDetails: (groupId: number) => void;
|
||||
loadMemberChat: (memberId: string | number) => void;
|
||||
closeRoleDocuments: (roleName: string) => void;
|
||||
newRequest: (params: RequestParams) => void;
|
||||
submitRequest: () => void;
|
||||
closeNewRequest: () => void;
|
||||
closeModal: (button: HTMLElement) => void;
|
||||
submitDocumentRequest: (documentId: number) => void;
|
||||
submitNewDocument: (event: Event) => void;
|
||||
submitCommonDocument: (event: Event) => void;
|
||||
signDocument: (documentId: number, processId: number, isCommonDocument: boolean) => void;
|
||||
confirmSignature: (documentId: number, processId: number, isCommonDocument: boolean) => void;
|
||||
}
|
||||
}
|
||||
import { RequestParams } from '../../models/signature.models';
|
||||
declare class SignatureElement extends HTMLElement {
|
||||
private selectedMemberId;
|
||||
private messagesMock;
|
||||
private dom;
|
||||
private notifications;
|
||||
private notificationBadge;
|
||||
private notificationBoard;
|
||||
private notificationBell;
|
||||
private selectedSignatories;
|
||||
private allMembers;
|
||||
private showAlert;
|
||||
private signDocument;
|
||||
constructor();
|
||||
private initMessageEvents;
|
||||
private initFileUpload;
|
||||
private calculateDuration;
|
||||
private canUserAccessDocument;
|
||||
private canUserSignDocument;
|
||||
private closeProcessDetails;
|
||||
private removeNotification;
|
||||
private renderNotifications;
|
||||
private updateNotificationBadge;
|
||||
private addNotification;
|
||||
private sendMessage;
|
||||
private showProcessDetails;
|
||||
private scrollToBottom;
|
||||
private loadMemberChat;
|
||||
private toggleMembers;
|
||||
private toggleRoles;
|
||||
private loadGroupList;
|
||||
private toggleUserList;
|
||||
private switchUser;
|
||||
private updateCurrentUserDisplay;
|
||||
private generateAutoReply;
|
||||
private sendFile;
|
||||
private fileList;
|
||||
private getFileList;
|
||||
private showRoleDocuments;
|
||||
private closeRoleDocuments;
|
||||
private handleFiles;
|
||||
private newRequest;
|
||||
private closeModal;
|
||||
private submitNewDocument;
|
||||
private submitCommonDocument;
|
||||
private submitRequest;
|
||||
private closeNewRequest;
|
||||
private submitDocumentRequest;
|
||||
private confirmSignature;
|
||||
private initializeEventListeners;
|
||||
connectedCallback(): void;
|
||||
}
|
||||
export { SignatureElement };
|
||||
5
dist/types/router.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import '../public/style/4nk.css';
|
||||
export declare let currentRoute: string;
|
||||
export declare function navigate(path: string): Promise<void>;
|
||||
export declare function init(): Promise<void>;
|
||||
export declare function registerAllListeners(): Promise<void>;
|
||||
43
dist/types/services/database.service.d.ts
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
export declare class Database {
|
||||
private static instance;
|
||||
private db;
|
||||
private dbName;
|
||||
private dbVersion;
|
||||
private serviceWorkerRegistration;
|
||||
private messageChannel;
|
||||
private messageChannelForGet;
|
||||
private serviceWorkerCheckIntervalId;
|
||||
private storeDefinitions;
|
||||
private constructor();
|
||||
static getInstance(): Promise<Database>;
|
||||
private init;
|
||||
getDb(): Promise<IDBDatabase>;
|
||||
getStoreList(): {
|
||||
[key: string]: string;
|
||||
};
|
||||
registerServiceWorker(path: string): Promise<void>;
|
||||
private waitForServiceWorkerActivation;
|
||||
private checkForUpdates;
|
||||
private handleServiceWorkerMessage;
|
||||
private handleDownloadList;
|
||||
private handleAddObjectResponse;
|
||||
private handleGetObjectResponse;
|
||||
addObject(payload: {
|
||||
storeName: string;
|
||||
object: any;
|
||||
key: any;
|
||||
}): Promise<void>;
|
||||
batchWriting(payload: {
|
||||
storeName: string;
|
||||
objects: {
|
||||
key: any;
|
||||
object: any;
|
||||
}[];
|
||||
}): Promise<void>;
|
||||
getObject(storeName: string, key: string): Promise<any | null>;
|
||||
dumpStore(storeName: string): Promise<Record<string, any>>;
|
||||
deleteObject(storeName: string, key: string): Promise<void>;
|
||||
clearStore(storeName: string): Promise<void>;
|
||||
requestStoreByIndex(storeName: string, indexName: string, request: string): Promise<any[]>;
|
||||
}
|
||||
export default Database;
|
||||
28
dist/types/services/modal.service.d.ts
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
import { RoleDefinition } from '../../pkg/sdk_client.js';
|
||||
interface ConfirmationModalOptions {
|
||||
title: string;
|
||||
content: string;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
}
|
||||
export default class ModalService {
|
||||
private static instance;
|
||||
private stateId;
|
||||
private processId;
|
||||
private constructor();
|
||||
private paired_addresses;
|
||||
private modal;
|
||||
static getInstance(): Promise<ModalService>;
|
||||
openLoginModal(myAddress: string, receiverAddress: string): void;
|
||||
injectModal(members: any[]): Promise<void>;
|
||||
injectCreationModal(members: any[]): Promise<void>;
|
||||
injectWaitingModal(): Promise<void>;
|
||||
injectValidationModal(processDiff: any): Promise<void>;
|
||||
closeValidationModal(): Promise<void>;
|
||||
openPairingConfirmationModal(roleDefinition: Record<string, RoleDefinition>, processId: string, stateId: string): Promise<void>;
|
||||
confirmLogin(): void;
|
||||
closeLoginModal(): Promise<void>;
|
||||
showConfirmationModal(options: ConfirmationModalOptions, fullscreen?: boolean): Promise<boolean>;
|
||||
closeConfirmationModal(): Promise<void>;
|
||||
}
|
||||
export {};
|
||||
171
dist/types/services/service.d.ts
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
import { ApiReturn, Device, Member, MerkleProofResult, Process, ProcessState, RoleDefinition, SecretsStore, UserDiff } from '../../pkg/sdk_client.js';
|
||||
import { BackUp } from '~/models/backup.model';
|
||||
export declare const U32_MAX = 4294967295;
|
||||
export default class Services {
|
||||
private static initializing;
|
||||
private static instance;
|
||||
private processId;
|
||||
private stateId;
|
||||
private sdkClient;
|
||||
private processesCache;
|
||||
private myProcesses;
|
||||
private notifications;
|
||||
private subscriptions;
|
||||
private database;
|
||||
private routingInstance;
|
||||
private relayAddresses;
|
||||
private membersList;
|
||||
private currentBlockHeight;
|
||||
private constructor();
|
||||
static getInstance(): Promise<Services>;
|
||||
init(): Promise<void>;
|
||||
setProcessId(processId: string | null): void;
|
||||
setStateId(stateId: string | null): void;
|
||||
getProcessId(): string | null;
|
||||
getStateId(): string | null;
|
||||
/**
|
||||
* Calls `this.addWebsocketConnection` for each `wsurl` in relayAddresses.
|
||||
* Waits for at least one handshake message before returning.
|
||||
*/
|
||||
connectAllRelays(): Promise<void>;
|
||||
addWebsocketConnection(url: string): Promise<void>;
|
||||
/**
|
||||
* Add or update a key/value pair in relayAddresses.
|
||||
* @param wsurl - The WebSocket URL (key).
|
||||
* @param spAddress - The SP Address (value).
|
||||
*/
|
||||
updateRelay(wsurl: string, spAddress: string): void;
|
||||
/**
|
||||
* Retrieve the spAddress for a given wsurl.
|
||||
* @param wsurl - The WebSocket URL to look up.
|
||||
* @returns The SP Address if found, or undefined if not.
|
||||
*/
|
||||
getSpAddress(wsurl: string): string | undefined;
|
||||
/**
|
||||
* Get all key/value pairs from relayAddresses.
|
||||
* @returns An array of objects containing wsurl and spAddress.
|
||||
*/
|
||||
getAllRelays(): {
|
||||
wsurl: string;
|
||||
spAddress: string;
|
||||
}[];
|
||||
/**
|
||||
* Print all key/value pairs for debugging.
|
||||
*/
|
||||
printAllRelays(): void;
|
||||
isPaired(): boolean;
|
||||
unpairDevice(): Promise<void>;
|
||||
getSecretForAddress(address: string): Promise<string | null>;
|
||||
getAllSecrets(): Promise<SecretsStore>;
|
||||
getAllDiffs(): Promise<Record<string, UserDiff>>;
|
||||
getDiffByValue(value: string): Promise<UserDiff | null>;
|
||||
private getTokensFromFaucet;
|
||||
checkConnections(process: Process, stateId?: string | null): Promise<void>;
|
||||
connectAddresses(addresses: string[]): Promise<ApiReturn>;
|
||||
private ensureSufficientAmount;
|
||||
private waitForAmount;
|
||||
createPairingProcess(userName: string, pairWith: string[]): Promise<ApiReturn>;
|
||||
private isFileBlob;
|
||||
private splitData;
|
||||
createProcess(privateData: Record<string, any>, publicData: Record<string, any>, roles: Record<string, RoleDefinition>): Promise<ApiReturn>;
|
||||
/**
|
||||
* Déclenche un transfert automatique de fonds du wallet mining vers le relay
|
||||
*/
|
||||
private triggerAutomaticFundsTransfer;
|
||||
updateProcess(process: Process, privateData: Record<string, any>, publicData: Record<string, any>, roles: Record<string, RoleDefinition> | null): Promise<ApiReturn>;
|
||||
createPrdUpdate(processId: string, stateId: string): Promise<ApiReturn>;
|
||||
createPrdResponse(processId: string, stateId: string): Promise<ApiReturn>;
|
||||
approveChange(processId: string, stateId: string): Promise<ApiReturn>;
|
||||
rejectChange(processId: string, stateId: string): Promise<ApiReturn>;
|
||||
resetDevice(): Promise<void>;
|
||||
sendNewTxMessage(message: string): void;
|
||||
sendCommitMessage(message: string): void;
|
||||
sendCipherMessages(ciphers: string[]): void;
|
||||
sendFaucetMessage(message: string): void;
|
||||
parseCipher(message: string): Promise<void>;
|
||||
parseNewTx(newTxMsg: string): Promise<void>;
|
||||
handleApiReturn(apiReturn: ApiReturn): Promise<void>;
|
||||
openPairingConfirmationModal(processId: string): Promise<void>;
|
||||
confirmPairing(): Promise<void>;
|
||||
updateDevice(): Promise<void>;
|
||||
pairDevice(processId: string, spAddressList: string[]): void;
|
||||
getAmount(): BigInt;
|
||||
getDeviceAddress(): string;
|
||||
dumpDeviceFromMemory(): Device;
|
||||
dumpNeuteredDevice(): Device | null;
|
||||
getPairingProcessId(): string;
|
||||
saveDeviceInDatabase(device: Device): Promise<void>;
|
||||
getDeviceFromDatabase(): Promise<Device | null>;
|
||||
getMemberFromDevice(): Promise<string[] | null>;
|
||||
isChildRole(parent: any, child: any): boolean;
|
||||
rolesContainsUs(roles: Record<string, RoleDefinition>): boolean;
|
||||
rolesContainsMember(roles: Record<string, RoleDefinition>, pairingProcessId: string): boolean;
|
||||
dumpWallet(): Promise<any>;
|
||||
createFaucetMessage(): any;
|
||||
createNewDevice(): Promise<string>;
|
||||
restoreDevice(device: Device): void;
|
||||
updateDeviceBlockHeight(): Promise<void>;
|
||||
private removeProcess;
|
||||
batchSaveProcessesToDb(processes: Record<string, Process>): Promise<void>;
|
||||
saveProcessToDb(processId: string, process: Process): Promise<void>;
|
||||
saveBlobToDb(hash: string, data: Blob): Promise<void>;
|
||||
getBlobFromDb(hash: string): Promise<Blob | null>;
|
||||
saveDataToStorage(hash: string, storages: string[], data: Blob, ttl: number | null): Promise<void>;
|
||||
fetchValueFromStorage(hash: string): Promise<ArrayBuffer | null>;
|
||||
getDiffByValueFromDb(hash: string): Promise<UserDiff | null>;
|
||||
saveDiffsToDb(diffs: UserDiff[]): Promise<void>;
|
||||
getProcess(processId: string): Promise<Process | null>;
|
||||
getProcesses(): Promise<Record<string, Process>>;
|
||||
restoreProcessesFromBackUp(processes: Record<string, Process>): Promise<void>;
|
||||
restoreProcessesFromDB(): Promise<void>;
|
||||
clearSecretsFromDB(): Promise<void>;
|
||||
restoreSecretsFromBackUp(secretsStore: SecretsStore): Promise<void>;
|
||||
restoreSecretsFromDB(): Promise<void>;
|
||||
decodeValue(value: number[]): any | null;
|
||||
decryptAttribute(processId: string, state: ProcessState, attribute: string): Promise<any | null>;
|
||||
getNotifications(): any[] | null;
|
||||
setNotifications(notifications: any[]): void;
|
||||
importJSON(backup: BackUp): Promise<void>;
|
||||
createBackUp(): Promise<BackUp | null>;
|
||||
device1: boolean;
|
||||
device2Ready: boolean;
|
||||
resetState(): void;
|
||||
handleHandshakeMsg(url: string, parsedMsg: any): Promise<void>;
|
||||
private lookForStateId;
|
||||
/**
|
||||
* Waits for at least one handshake message to be received from any connected relay.
|
||||
* This ensures that the relay addresses are fully populated and the member list is updated.
|
||||
* @returns A promise that resolves when at least one handshake message is received.
|
||||
*/
|
||||
private waitForHandshakeMessage;
|
||||
/**
|
||||
* Retourne la liste de tous les membres ordonnés par leur process id
|
||||
* @returns Un tableau contenant tous les membres
|
||||
*/
|
||||
getAllMembersSorted(): Record<string, Member>;
|
||||
getAllMembers(): Record<string, Member>;
|
||||
getAddressesForMemberId(memberId: string): string[] | null;
|
||||
compareMembers(memberA: string[], memberB: string[]): boolean;
|
||||
handleCommitError(response: string): Promise<void>;
|
||||
getRoles(process: Process): Record<string, RoleDefinition> | null;
|
||||
getPublicData(process: Process): Record<string, any> | null;
|
||||
getProcessName(process: Process): string | null;
|
||||
getMyProcesses(): Promise<string[] | null>;
|
||||
requestDataFromPeers(processId: string, stateIds: string[], roles: Record<string, RoleDefinition>[]): Promise<void>;
|
||||
hexToBlob(hexString: string): Blob;
|
||||
hexToUInt8Array(hexString: string): Uint8Array;
|
||||
blobToHex(blob: Blob): Promise<string>;
|
||||
getHashForFile(commitedIn: string, label: string, fileBlob: {
|
||||
type: string;
|
||||
data: Uint8Array;
|
||||
}): string;
|
||||
getMerkleProofForFile(processState: ProcessState, attributeName: string): MerkleProofResult;
|
||||
validateMerkleProof(proof: MerkleProofResult, hash: string): boolean;
|
||||
getLastCommitedState(process: Process): ProcessState | null;
|
||||
getLastCommitedStateIndex(process: Process): number | null;
|
||||
getUncommitedStates(process: Process): ProcessState[];
|
||||
getStateFromId(process: Process, stateId: string): ProcessState | null;
|
||||
getNextStateAfterId(process: Process, stateId: string): ProcessState | null;
|
||||
isPairingProcess(roles: Record<string, RoleDefinition>): boolean;
|
||||
updateMemberPublicName(process: Process, newName: string): Promise<ApiReturn>;
|
||||
}
|
||||
4
dist/types/services/storage.service.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
export declare function storeData(servers: string[], key: string, value: Blob, ttl: number | null): Promise<AxiosResponse | null>;
|
||||
export declare function retrieveData(servers: string[], key: string): Promise<ArrayBuffer | null>;
|
||||
export declare function testData(url: string, key: string): Promise<boolean | null>;
|
||||
18
dist/types/services/token.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
interface TokenPair {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
}
|
||||
export default class TokenService {
|
||||
private static instance;
|
||||
private readonly SECRET_KEY;
|
||||
private readonly ACCESS_TOKEN_EXPIRATION;
|
||||
private readonly REFRESH_TOKEN_EXPIRATION;
|
||||
private readonly encoder;
|
||||
private constructor();
|
||||
static getInstance(): Promise<TokenService>;
|
||||
private getOrCreateSecret;
|
||||
generateSessionToken(origin: string): Promise<TokenPair>;
|
||||
validateToken(token: string, origin: string): Promise<boolean>;
|
||||
refreshAccessToken(refreshToken: string, origin: string): Promise<string | null>;
|
||||
}
|
||||
export {};
|
||||
1
dist/types/utils/document.utils.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export declare function getCorrectDOM(componentTag: string): Node;
|
||||
4
dist/types/utils/html.utils.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
export declare function interpolate(template: string, data: {
|
||||
[key: string]: string;
|
||||
}): string;
|
||||
export declare function getCorrectDOM(componentTag: string): Node;
|
||||
12
dist/types/utils/messageMock.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
declare class MessageStore {
|
||||
private readonly STORAGE_KEY;
|
||||
private messages;
|
||||
constructor();
|
||||
private loadFromLocalStorage;
|
||||
getMessages(): any[];
|
||||
setMessages(messages: any[]): void;
|
||||
private saveToLocalStorage;
|
||||
addMessage(memberId: string | number, message: any): void;
|
||||
}
|
||||
export declare const messageStore: MessageStore;
|
||||
export {};
|
||||
23
dist/types/utils/notification.store.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
interface INotification {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
time?: string;
|
||||
memberId?: string;
|
||||
}
|
||||
declare class NotificationStore {
|
||||
private static instance;
|
||||
private notifications;
|
||||
private constructor();
|
||||
static getInstance(): NotificationStore;
|
||||
addNotification(notification: INotification): void;
|
||||
removeNotification(index: number): void;
|
||||
getNotifications(): INotification[];
|
||||
private saveToLocalStorage;
|
||||
private loadFromLocalStorage;
|
||||
private updateUI;
|
||||
private renderNotificationBoard;
|
||||
refreshNotifications(): void;
|
||||
}
|
||||
export declare const notificationStore: NotificationStore;
|
||||
export {};
|
||||
5
dist/types/utils/service.utils.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export declare function splitPrivateData(data: Record<string, any>, privateFields: string[]): {
|
||||
privateData: Record<string, any>;
|
||||
publicData: Record<string, any>;
|
||||
};
|
||||
export declare function isValid32ByteHex(value: string): boolean;
|
||||
8
dist/types/utils/sp-address.utils.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
export declare function copyToClipboard(fullAddress: string): Promise<void>;
|
||||
export declare function generateEmojiList(): string[];
|
||||
export declare function addressToEmoji(text: string): Promise<string>;
|
||||
export declare function displayEmojis(text: string): Promise<void>;
|
||||
export declare function initAddressInput(): void;
|
||||
export declare function prepareAndSendPairingTx(): Promise<void>;
|
||||
export declare function generateQRCode(spAddress: string): Promise<void>;
|
||||
export declare function generateCreateBtn(): Promise<void>;
|
||||
2
dist/types/utils/subscription.utils.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export declare function cleanSubscriptions(): void;
|
||||
export declare function addSubscription(element: Element | Document, event: any, eventHandler: EventListenerOrEventListenerObject): void;
|
||||
5
dist/types/websockets.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import { AnkFlag } from '.././pkg/sdk_client.js';
|
||||
export declare function initWebsocket(url: string): Promise<void>;
|
||||
export declare function sendMessage(flag: AnkFlag, message: string): void;
|
||||
export declare function getUrl(): string;
|
||||
export declare function close(): void;
|
||||
|
Before Width: | Height: | Size: 1.9 MiB |
14
index.html
@ -9,6 +9,7 @@
|
||||
<link rel="stylesheet" href="./style/4nk.css">
|
||||
<script src="https://unpkg.com/html5-qrcode"></script>
|
||||
<title>4NK Application</title>
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
</head>
|
||||
<body>
|
||||
<div id="header-container"></div>
|
||||
@ -17,9 +18,18 @@
|
||||
</div>
|
||||
<!-- <script type="module" src="/src/index.ts"></script> -->
|
||||
<script type="module">
|
||||
import { init } from '/src/router.ts';
|
||||
// Charge le module WASM (ESM auto-start, pas d'export default)
|
||||
import './pkg/sdk_client.js';
|
||||
|
||||
// Initialise l'application
|
||||
import { init as initRouter } from '/src/router.ts';
|
||||
|
||||
(async () => {
|
||||
await init();
|
||||
try {
|
||||
await initRouter();
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize application:', error);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
3833
package-lock.json
generated
@ -5,11 +5,12 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build_wasm": "wasm-pack build --out-dir ../ihm_client_dev1/pkg ../sdk_client --target bundler --dev",
|
||||
"build_wasm": "wasm-pack build --out-dir ../ihm_client/pkg ../sdk_client --target bundler --dev",
|
||||
"start": "vite --host 0.0.0.0",
|
||||
"build": "tsc && vite build",
|
||||
"deploy": "sudo cp -r dist/* /var/www/html/",
|
||||
"prettify": "prettier --config ./.prettierrc --write \"src/**/*{.ts,.html,.css,.js}\""
|
||||
"prettify": "prettier --config ./.prettierrc --write \"src/**/*{.ts,.html,.css,.js}\"",
|
||||
"build:dist": "tsc -p tsconfig.build.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@ -30,11 +31,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/elements": "^19.0.1",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"axios": "^1.7.8",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"jose": "^6.0.11",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"qr-scanner": "^1.4.2",
|
||||
"qrcode": "^1.5.3",
|
||||
"sweetalert2": "^11.14.5",
|
||||
|
||||
79
pkg/.gitignore
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
# 4NK Environment - Git Ignore
|
||||
# ============================
|
||||
confs/
|
||||
# Dossiers de sauvegarde des scripts
|
||||
**/backup/
|
||||
**/*backup*
|
||||
|
||||
**/.cargo/
|
||||
|
||||
# Fichiers temporaires
|
||||
**/*.tmp*
|
||||
**/*.temp*
|
||||
**/*.log*
|
||||
**/*.pid*
|
||||
|
||||
# Fichiers de configuration locale
|
||||
**/*.env*
|
||||
**/*.conf*
|
||||
**/*.yaml*
|
||||
**/*.yml*
|
||||
**/*.ini*
|
||||
**/*.json*
|
||||
**/*.toml*
|
||||
**/*.lock*
|
||||
|
||||
# Données et logs
|
||||
**/*.logs*
|
||||
**/*.data
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# Certificats et clés
|
||||
**/*.key
|
||||
**/*.pem
|
||||
**/*.crt
|
||||
**/*.p12
|
||||
**/*.pfx
|
||||
ssl/
|
||||
certs/
|
||||
|
||||
# Docker
|
||||
**/*.docker*
|
||||
|
||||
# Cache et build
|
||||
**/*.node_modules/
|
||||
**/*.dist/
|
||||
**/*build/
|
||||
**/*target/
|
||||
**/*.*.o
|
||||
**/*.so
|
||||
**/*.dylib
|
||||
|
||||
# IDE et éditeurs
|
||||
**/*.vscode/
|
||||
**/*.idea/
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
**/*~
|
||||
|
||||
# OS
|
||||
**/*.DS_Store
|
||||
**/*Thumbs.db
|
||||
**/*tmp*
|
||||
|
||||
# Git
|
||||
**/*.git/
|
||||
**/*.orig*
|
||||
|
||||
# Backup des projets existants
|
||||
**/*backup*
|
||||
|
||||
|
||||
**/*wallet*
|
||||
**/*keys*
|
||||
|
||||
**/*node_modules*
|
||||
**/*cursor*
|
||||
**/*pid*
|
||||
**/*next*
|
||||
17
pkg/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "sdk_client",
|
||||
"type": "module",
|
||||
"version": "0.1.3",
|
||||
"files": [
|
||||
"sdk_client_bg.wasm",
|
||||
"sdk_client.js",
|
||||
"sdk_client_bg.js",
|
||||
"sdk_client.d.ts"
|
||||
],
|
||||
"main": "sdk_client.js",
|
||||
"types": "sdk_client.d.ts",
|
||||
"sideEffects": [
|
||||
"./sdk_client.js",
|
||||
"./snippets/*"
|
||||
]
|
||||
}
|
||||
249
pkg/sdk_client.d.ts
vendored
Normal file
@ -0,0 +1,249 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export function setup(): void;
|
||||
export function get_address(): string;
|
||||
export function get_member(): Member;
|
||||
export function restore_device(device: any): void;
|
||||
export function create_device_from_sp_wallet(sp_wallet: string): string;
|
||||
export function create_new_device(birthday: number, network_str: string): string;
|
||||
export function scan_blocks(tip_height: number, blindbit_url: string): Promise<void>;
|
||||
export function is_paired(): boolean;
|
||||
export function pair_device(process_id: string, sp_addresses: string[]): void;
|
||||
export function unpair_device(): void;
|
||||
export function dump_wallet(): string;
|
||||
export function reset_shared_secrets(): void;
|
||||
export function set_shared_secrets(secrets: string): void;
|
||||
export function get_pairing_process_id(): string;
|
||||
export function dump_device(): Device;
|
||||
export function dump_neutered_device(): Device;
|
||||
export function reset_device(): void;
|
||||
export function get_txid(transaction: string): string;
|
||||
export function get_prevouts(transaction: string): string[];
|
||||
export function get_opreturn(transaction: string): string;
|
||||
export function process_commit_new_state(process: Process, state_id: string, new_tip: string): Process;
|
||||
export function parse_new_tx(new_tx_msg: string, block_height: number, members_list: OutPointMemberMap): ApiReturn;
|
||||
export function parse_cipher(cipher_msg: string, members_list: OutPointMemberMap, processes: OutPointProcessMap): ApiReturn;
|
||||
export function get_outputs(): any;
|
||||
export function get_available_amount(): bigint;
|
||||
/**
|
||||
* We send a transaction that pays at least one output to each address
|
||||
* The goal can be to establish a shared_secret to be used as an encryption key for further communication
|
||||
* or if the recipient is a relay it can be the init transaction for a new process
|
||||
*/
|
||||
export function create_transaction(addresses: string[], fee_rate: number): ApiReturn;
|
||||
export function sign_transaction(partial_tx: TsUnsignedTransaction): ApiReturn;
|
||||
export function create_new_process(private_data: Pcd, roles: Roles, public_data: Pcd, relay_address: string, fee_rate: number, members_list: OutPointMemberMap): ApiReturn;
|
||||
export function update_process(process: Process, new_attributes: Pcd, roles: Roles, new_public_data: Pcd, members_list: OutPointMemberMap): ApiReturn;
|
||||
export function request_data(process_id: string, state_ids_str: string[], roles: any, members_list: OutPointMemberMap): ApiReturn;
|
||||
export function create_update_message(process: Process, state_id: string, members_list: OutPointMemberMap): ApiReturn;
|
||||
export function validate_state(process: Process, state_id: string, members_list: OutPointMemberMap): ApiReturn;
|
||||
export function refuse_state(process: Process, state_id: string, members_list: OutPointMemberMap): ApiReturn;
|
||||
export function evaluate_state(process: Process, state_id: string, members_list: OutPointMemberMap): ApiReturn;
|
||||
export function create_response_prd(process: Process, state_id: string, members_list: OutPointMemberMap): ApiReturn;
|
||||
export function create_faucet_msg(): string;
|
||||
export function get_storages(process_outpoint: string): string[];
|
||||
export function is_child_role(parent_roles: string, child_roles: string): void;
|
||||
export function decrypt_data(key: Uint8Array, data: Uint8Array): Uint8Array;
|
||||
export function encode_binary(data: any): Pcd;
|
||||
export function encode_json(json_data: any): Pcd;
|
||||
export function decode_value(value: Uint8Array): any;
|
||||
export function hash_value(value: any, commited_in: string, label: string): string;
|
||||
/**
|
||||
* Generate a merkle proof for a specific attribute in a process state.
|
||||
*
|
||||
* This function creates a merkle proof that proves the existence of a specific attribute
|
||||
* in a given state of a process. The proof can be used to verify that the attribute
|
||||
* was indeed part of the state without revealing the entire state.
|
||||
*
|
||||
* # Arguments
|
||||
* * `process_state` - The process state object as a JavaScript value
|
||||
* * `attribute_name` - The name of the attribute to generate a proof for
|
||||
*
|
||||
* # Returns
|
||||
* A MerkleProofResult object containing:
|
||||
* * `proof` - The merkle proof as a hex string
|
||||
* * `root` - The merkle root (state_id) as a hex string
|
||||
* * `attribute` - The attribute name that was proven
|
||||
* * `attribute_index` - The index of the attribute in the merkle tree
|
||||
* * `total_leaves_count` - The total number of leaves in the merkle tree
|
||||
*
|
||||
* # Errors
|
||||
* * "Failed to deserialize process state" - If the process state cannot be deserialized from JsValue
|
||||
* * "Attribute not found in state" - If the attribute doesn't exist in the state
|
||||
*/
|
||||
export function get_merkle_proof(process_state: any, attribute_name: string): MerkleProofResult;
|
||||
/**
|
||||
* Validate a merkle proof for a specific attribute.
|
||||
*
|
||||
* This function verifies that a merkle proof is valid and proves the existence
|
||||
* of a specific attribute in a given state. It checks that the proof correctly
|
||||
* leads to the claimed root when combined with the attribute hash.
|
||||
*
|
||||
* # Arguments
|
||||
* * `proof_result` - a JsValue expected to contain a MerkleProofResult with the proof and metadata
|
||||
* * `hash` - The hash of the attribute data as a hex string (the leaf value)
|
||||
*
|
||||
* # Returns
|
||||
* A boolean indicating whether the proof is valid
|
||||
*
|
||||
* # Errors
|
||||
* * "serde_wasm_bindgen deserialization error" - If the proof is not a valid MerkleProofResult
|
||||
* * "Invalid proof format" - If the proof cannot be parsed
|
||||
* * "Invalid hash format" - If the hash is not a valid 32-byte hex string
|
||||
* * "Invalid root format" - If the root is not a valid 32-byte hex string
|
||||
*/
|
||||
export function validate_merkle_proof(proof_result: any, hash: string): boolean;
|
||||
export type DiffStatus = "None" | "Rejected" | "Validated";
|
||||
|
||||
export interface UserDiff {
|
||||
process_id: string;
|
||||
state_id: string;
|
||||
value_commitment: string;
|
||||
field: string;
|
||||
roles: Roles;
|
||||
description: string | null;
|
||||
notify_user: boolean;
|
||||
need_validation: boolean;
|
||||
validation_status: DiffStatus;
|
||||
storages: string[];
|
||||
}
|
||||
|
||||
export interface UpdatedProcess {
|
||||
process_id: OutPoint;
|
||||
current_process: Process;
|
||||
diffs: UserDiff[];
|
||||
encrypted_data: Record<string, string>;
|
||||
validated_state: number[] | null;
|
||||
}
|
||||
|
||||
export interface ApiReturn {
|
||||
secrets: SecretsStore | null;
|
||||
updated_process: UpdatedProcess | null;
|
||||
new_tx_to_send: NewTxMessage | null;
|
||||
ciphers_to_send: string[];
|
||||
commit_to_send: CommitMessage | null;
|
||||
push_to_storage: string[];
|
||||
partial_tx: TsUnsignedTransaction | null;
|
||||
}
|
||||
|
||||
export interface encryptWithNewKeyResult {
|
||||
cipher: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface MerkleProofResult {
|
||||
proof: string;
|
||||
root: string;
|
||||
attribute: string;
|
||||
attribute_index: number;
|
||||
total_leaves_count: number;
|
||||
}
|
||||
|
||||
export interface Prd {
|
||||
prd_type: PrdType;
|
||||
process_id: OutPoint;
|
||||
sender: OutPoint | null;
|
||||
keys: Record<string, number[]>;
|
||||
pcd_commitments: PcdCommitments;
|
||||
validation_tokens: Proof[];
|
||||
roles: Roles;
|
||||
public_data: Pcd;
|
||||
payload: string;
|
||||
proof: Proof | null;
|
||||
}
|
||||
|
||||
export type PrdType = "None" | "Connect" | "Message" | "Update" | "List" | "Response" | "Confirm" | "TxProposal" | "Request";
|
||||
|
||||
export interface Device {
|
||||
sp_wallet: SpWallet;
|
||||
pairing_process_commitment: OutPoint | null;
|
||||
paired_member: Member;
|
||||
}
|
||||
|
||||
export type OutPointProcessMap = Record<OutPoint, Process>;
|
||||
|
||||
export type OutPointMemberMap = Record<OutPoint, Member>;
|
||||
|
||||
export interface HandshakeMessage {
|
||||
sp_address: string;
|
||||
peers_list: OutPointMemberMap;
|
||||
processes_list: OutPointProcessMap;
|
||||
chain_tip: number;
|
||||
}
|
||||
|
||||
export interface NewTxMessage {
|
||||
transaction: string;
|
||||
tweak_data: string | null;
|
||||
error: AnkError | null;
|
||||
}
|
||||
|
||||
export interface FaucetMessage {
|
||||
sp_address: string;
|
||||
commitment: string;
|
||||
error: AnkError | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message sent to the server to commit some state in a transaction
|
||||
* Client must first send a commit message with empty validation_tokens
|
||||
* Relay will ignore a commit message for an update he\'s not aware of that also bears validation_tokens
|
||||
*/
|
||||
export interface CommitMessage {
|
||||
process_id: OutPoint;
|
||||
pcd_commitment: PcdCommitments;
|
||||
roles: Roles;
|
||||
public_data: Pcd;
|
||||
validation_tokens: Proof[];
|
||||
error: AnkError | null;
|
||||
}
|
||||
|
||||
export type AnkFlag = "NewTx" | "Faucet" | "Cipher" | "Commit" | "Handshake" | "Sync" | "Unknown";
|
||||
|
||||
export type TsUnsignedTransaction = SilentPaymentUnsignedTransaction;
|
||||
|
||||
export interface SecretsStore {
|
||||
shared_secrets: Record<SilentPaymentAddress, AnkSharedSecretHash>;
|
||||
unconfirmed_secrets: AnkSharedSecretHash[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A process is basically a succession of states
|
||||
* The latest state MUST be an empty state with only the commited_in field set at the last unspent outpoint
|
||||
* Commiting this last empty state in a transaction is called obliterating a process, basically terminating it
|
||||
*/
|
||||
export interface Process {
|
||||
states: ProcessState[];
|
||||
}
|
||||
|
||||
export interface ProcessState {
|
||||
commited_in: OutPoint;
|
||||
pcd_commitment: Record<string, string>;
|
||||
state_id: string;
|
||||
keys: Record<string, string>;
|
||||
validation_tokens: Proof[];
|
||||
public_data: Pcd;
|
||||
roles: Record<string, RoleDefinition>;
|
||||
}
|
||||
|
||||
export type Roles = Record<string, RoleDefinition>;
|
||||
|
||||
export interface RoleDefinition {
|
||||
members: OutPoint[];
|
||||
validation_rules: ValidationRule[];
|
||||
storages: string[];
|
||||
}
|
||||
|
||||
export interface ValidationRule {
|
||||
quorum: number;
|
||||
fields: string[];
|
||||
min_sig_member: number;
|
||||
}
|
||||
|
||||
export type PcdCommitments = Record<string, string>;
|
||||
|
||||
export type Pcd = Record<string, number[]>;
|
||||
|
||||
export interface Member {
|
||||
sp_addresses: string[];
|
||||
}
|
||||
|
||||
5
pkg/sdk_client.js
Normal file
@ -0,0 +1,5 @@
|
||||
import * as wasm from "./sdk_client_bg.wasm";
|
||||
export * from "./sdk_client_bg.js";
|
||||
import { __wbg_set_wasm } from "./sdk_client_bg.js";
|
||||
__wbg_set_wasm(wasm);
|
||||
wasm.__wbindgen_start();
|
||||
1691
pkg/sdk_client_bg.js
Normal file
BIN
pkg/sdk_client_bg.wasm
Normal file
73
pkg/sdk_client_bg.wasm.d.ts
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export const setup: () => void;
|
||||
export const get_address: () => [number, number, number, number];
|
||||
export const get_member: () => [number, number, number];
|
||||
export const restore_device: (a: any) => [number, number];
|
||||
export const create_device_from_sp_wallet: (a: number, b: number) => [number, number, number, number];
|
||||
export const create_new_device: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const scan_blocks: (a: number, b: number, c: number) => any;
|
||||
export const is_paired: () => [number, number, number];
|
||||
export const pair_device: (a: number, b: number, c: number, d: number) => [number, number];
|
||||
export const unpair_device: () => [number, number];
|
||||
export const dump_wallet: () => [number, number, number, number];
|
||||
export const reset_shared_secrets: () => [number, number];
|
||||
export const set_shared_secrets: (a: number, b: number) => [number, number];
|
||||
export const get_pairing_process_id: () => [number, number, number, number];
|
||||
export const dump_device: () => [number, number, number];
|
||||
export const dump_neutered_device: () => [number, number, number];
|
||||
export const reset_device: () => [number, number];
|
||||
export const get_txid: (a: number, b: number) => [number, number, number, number];
|
||||
export const get_prevouts: (a: number, b: number) => [number, number, number, number];
|
||||
export const get_opreturn: (a: number, b: number) => [number, number, number, number];
|
||||
export const process_commit_new_state: (a: any, b: number, c: number, d: number, e: number) => [number, number, number];
|
||||
export const parse_new_tx: (a: number, b: number, c: number, d: any) => [number, number, number];
|
||||
export const parse_cipher: (a: number, b: number, c: any, d: any) => [number, number, number];
|
||||
export const get_outputs: () => [number, number, number];
|
||||
export const get_available_amount: () => [bigint, number, number];
|
||||
export const create_transaction: (a: number, b: number, c: number) => [number, number, number];
|
||||
export const sign_transaction: (a: any) => [number, number, number];
|
||||
export const create_new_process: (a: any, b: any, c: any, d: number, e: number, f: number, g: any) => [number, number, number];
|
||||
export const update_process: (a: any, b: any, c: any, d: any, e: any) => [number, number, number];
|
||||
export const request_data: (a: number, b: number, c: number, d: number, e: any, f: any) => [number, number, number];
|
||||
export const create_update_message: (a: any, b: number, c: number, d: any) => [number, number, number];
|
||||
export const validate_state: (a: any, b: number, c: number, d: any) => [number, number, number];
|
||||
export const refuse_state: (a: any, b: number, c: number, d: any) => [number, number, number];
|
||||
export const evaluate_state: (a: any, b: number, c: number, d: any) => [number, number, number];
|
||||
export const create_response_prd: (a: any, b: number, c: number, d: any) => [number, number, number];
|
||||
export const create_faucet_msg: () => [number, number, number, number];
|
||||
export const get_storages: (a: number, b: number) => [number, number, number, number];
|
||||
export const is_child_role: (a: number, b: number, c: number, d: number) => [number, number];
|
||||
export const decrypt_data: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
export const encode_binary: (a: any) => [number, number, number];
|
||||
export const encode_json: (a: any) => [number, number, number];
|
||||
export const decode_value: (a: number, b: number) => [number, number, number];
|
||||
export const hash_value: (a: any, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
||||
export const get_merkle_proof: (a: any, b: number, c: number) => [number, number, number];
|
||||
export const validate_merkle_proof: (a: any, b: number, c: number) => [number, number, number];
|
||||
export const rust_zstd_wasm_shim_qsort: (a: number, b: number, c: number, d: number) => void;
|
||||
export const rust_zstd_wasm_shim_malloc: (a: number) => number;
|
||||
export const rust_zstd_wasm_shim_memcmp: (a: number, b: number, c: number) => number;
|
||||
export const rust_zstd_wasm_shim_calloc: (a: number, b: number) => number;
|
||||
export const rust_zstd_wasm_shim_free: (a: number) => void;
|
||||
export const rust_zstd_wasm_shim_memcpy: (a: number, b: number, c: number) => number;
|
||||
export const rust_zstd_wasm_shim_memmove: (a: number, b: number, c: number) => number;
|
||||
export const rust_zstd_wasm_shim_memset: (a: number, b: number, c: number) => number;
|
||||
export const rustsecp256k1_v0_9_2_context_create: (a: number) => number;
|
||||
export const rustsecp256k1_v0_9_2_context_destroy: (a: number) => void;
|
||||
export const rustsecp256k1_v0_9_2_default_illegal_callback_fn: (a: number, b: number) => void;
|
||||
export const rustsecp256k1_v0_9_2_default_error_callback_fn: (a: number, b: number) => void;
|
||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __wbindgen_exn_store: (a: number) => void;
|
||||
export const __externref_table_alloc: () => number;
|
||||
export const __wbindgen_export_4: WebAssembly.Table;
|
||||
export const __wbindgen_export_5: WebAssembly.Table;
|
||||
export const __externref_table_dealloc: (a: number) => void;
|
||||
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
export const __externref_drop_slice: (a: number, b: number) => void;
|
||||
export const wasm_bindgen__convert__closures_____invoke__hc142e2252e76ee8b: (a: number, b: number) => void;
|
||||
export const closure681_externref_shim: (a: number, b: number, c: any) => void;
|
||||
export const closure1281_externref_shim: (a: number, b: number, c: any, d: any) => void;
|
||||
export const __wbindgen_start: () => void;
|
||||
111
public/authorized-client.html
Normal file
@ -0,0 +1,111 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Redirection en cours…</title>
|
||||
<style>
|
||||
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 2rem; color: #0a0a0a; }
|
||||
.box { max-width: 720px; margin: 10vh auto; padding: 1.5rem; border: 1px solid #e5e7eb; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
|
||||
.muted { color: #6b7280; font-size: .95rem; }
|
||||
.error { color: #b91c1c; }
|
||||
.ok { color: #065f46; }
|
||||
code { background: #f3f4f6; padding: .2rem .35rem; border-radius: 6px; }
|
||||
a { color: #006BE0; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<h1>Connexion IdNot</h1>
|
||||
<p class="muted" id="status">Traitement du code d'autorisation…</p>
|
||||
<pre class="muted" id="details" style="white-space: pre-wrap"></pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function getQueryParam(name) {
|
||||
const params = new URLSearchParams(location.search);
|
||||
return params.get(name);
|
||||
}
|
||||
|
||||
function setCookie(name, value, days) {
|
||||
const expires = new Date(Date.now() + days * 864e5).toUTCString();
|
||||
// Domaine implicite: dev4.4nkweb.com (hébergement de cette page)
|
||||
document.cookie = name + '=' + encodeURIComponent(value) + '; Path=/; Expires=' + expires + '; SameSite=None; Secure';
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const code = getQueryParam('code');
|
||||
const statusEl = document.getElementById('status');
|
||||
const detailsEl = document.getElementById('details');
|
||||
|
||||
if (!code) {
|
||||
statusEl.textContent = 'Aucun code reçu dans la redirection IdNot.';
|
||||
statusEl.className = 'error';
|
||||
detailsEl.textContent = 'Paramètre attendu: ?code=…\nRetour à l\'espace: https://dev4.4nkweb.com/lecoffre/';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch('https://dev4.4nkweb.com/api/v1/idnot/auth', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': 'bridge_' + Math.random().toString(36).slice(2)
|
||||
},
|
||||
body: JSON.stringify({ code })
|
||||
});
|
||||
|
||||
const text = await resp.text();
|
||||
let data;
|
||||
try { data = JSON.parse(text); } catch (_) { data = null; }
|
||||
|
||||
if (!resp.ok) {
|
||||
statusEl.textContent = 'Connexion refusée (' + resp.status + ').';
|
||||
statusEl.className = 'error';
|
||||
detailsEl.textContent = (data && data.error && data.error.message) ? data.error.message : text;
|
||||
// Redirige néanmoins vers le front avec état d\'erreur afin d\'afficher un message utilisateur.
|
||||
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=' + encodeURIComponent(String(resp.status))); }, 600);
|
||||
return;
|
||||
}
|
||||
|
||||
// Attendu: { idNotUser, authToken }
|
||||
if (!data || !data.authToken) {
|
||||
statusEl.textContent = 'Réponse invalide du serveur.';
|
||||
statusEl.className = 'error';
|
||||
detailsEl.textContent = text;
|
||||
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=invalid_response'); }, 800);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stocker le jeton pour le domaine dev4 (utilisé par le front)
|
||||
setCookie('leCoffreAccessToken', data.authToken, 1);
|
||||
|
||||
statusEl.textContent = 'Connexion réussie, redirection…';
|
||||
statusEl.className = 'ok';
|
||||
detailsEl.textContent = '';
|
||||
|
||||
// Redirection vers le front avec token en hash en sauvegarde
|
||||
location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#token=' + encodeURIComponent(data.authToken));
|
||||
} catch (e) {
|
||||
statusEl.textContent = 'Erreur réseau lors de la connexion.';
|
||||
statusEl.className = 'error';
|
||||
detailsEl.textContent = String(e && e.message || e);
|
||||
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=network'); }, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 598 B |
5
public/favicon.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="256" height="256" rx="128" fill="white"/>
|
||||
<path d="M185.067 93.7936C185.647 94.7771 186.831 95.2561 187.94 94.9282L206.362 89.4058C207.345 89.1284 208 88.2206 208 87.2372V52.4634C208 49.9922 206.009 48 203.539 48H51.7297C49.4869 48 48.126 49.5131 48.126 51.7069L48 131.341V203.537C48 206.008 49.9909 208 52.4606 208H204.27C206.513 208 207.874 206.487 207.874 204.268L208 124.659V124.407C208 121.456 207.572 119.414 203.565 120.548L196.332 122.692C195.173 123.045 194.417 124.129 194.467 125.314L194.593 127.987C194.593 159.685 178.087 182.43 148.955 191.357C101.3 205.957 57.8283 168.536 61.6841 122.213C64.3554 90.4145 89.909 64.5925 121.662 61.6674C148.526 59.1962 172.442 72.6872 185.042 93.7431L185.067 93.7936Z" fill="#005BCB"/>
|
||||
<path d="M153.011 159.962C159.739 155.322 164.931 148.615 167.677 140.772C168.408 138.629 168.988 136.385 169.341 134.09C169.567 132.703 168.257 131.543 166.921 131.972L154.548 135.855C154.019 136.032 153.59 136.435 153.389 136.965C149.482 146.724 139.402 153.305 128.011 151.918C116.898 150.556 108.002 141.327 107.019 130.181C106.691 126.449 107.221 122.869 108.405 119.616C111.807 110.437 120.653 103.88 131.01 103.88C136.857 103.88 142.225 105.973 146.408 109.453C146.887 109.857 147.542 110.008 148.172 109.806L159.916 106.125C160.571 105.923 160.999 105.419 161.176 104.864C161.327 104.284 161.251 103.628 160.823 103.124C153.237 94.0206 141.544 88.4478 128.566 89.2295C128.415 89.2547 128.263 89.2799 128.112 89.2799L124.105 76.5202C123.601 74.8559 121.837 73.9228 120.174 74.4272L111.051 77.2767C109.388 77.8062 108.455 79.5714 108.985 81.2357L111.958 90.7425C112.488 92.4068 111.832 94.2224 110.371 95.1554C103.138 99.7448 97.4679 106.654 94.5194 114.824C93.3098 118.103 92.5538 121.582 92.3017 125.188C92.2261 126.525 92.2009 127.836 92.2513 129.122C92.3269 130.862 91.1929 132.426 89.5297 132.955L79.4997 136.183C77.8365 136.713 76.904 138.478 77.4081 140.142L80.2558 149.27C80.785 150.935 82.5491 151.842 84.2123 151.338L94.4186 148.035C96.0315 147.53 97.8207 148.11 98.7532 149.523C105.709 159.937 117.553 166.821 131.01 166.821C131.59 166.821 132.144 166.821 132.699 166.821C134.463 166.72 136.05 167.855 136.58 169.519L139.679 179.505C140.208 181.17 141.973 182.103 143.636 181.573L152.758 178.724C154.422 178.219 155.329 176.429 154.825 174.765L151.549 164.325C151.045 162.711 151.624 160.946 153.011 159.988V159.962Z" fill="#005BCB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@ -77,6 +77,99 @@ body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Confirmation Modal Styles */
|
||||
#confirmation-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.modal-confirmation {
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.modal-confirmation h3 {
|
||||
margin-bottom: 15px;
|
||||
color: var(--primary-color);
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.modal-confirmation p {
|
||||
margin: 8px 0;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.modal-footer button {
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--secondary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media only screen and (max-width: 600px) {
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
margin: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.modal-confirmation h3 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.modal-confirmation p {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-wrapper {
|
||||
position: fixed;
|
||||
|
||||
@ -427,24 +427,43 @@ body {
|
||||
|
||||
/* Style pour la modal de confirmation */
|
||||
.confirm-delete-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.confirm-delete-content {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1100;
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.confirm-delete-content h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.confirm-delete-content p {
|
||||
margin: 15px 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.confirm-delete-buttons {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.confirm-delete-buttons button {
|
||||
@ -452,25 +471,27 @@ body {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
.confirm-delete-buttons .confirm-btn {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
.confirm-delete-buttons .confirm-btn:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
.confirm-delete-buttons .cancel-btn {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.delete-account-section {
|
||||
border-top: 1px solid #eee;
|
||||
padding-top: 15px;
|
||||
margin-top: 15px;
|
||||
.confirm-delete-buttons .cancel-btn:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------- Export--------------------------------------*/
|
||||
.export-section {
|
||||
margin: 20px 0;
|
||||
@ -1423,3 +1444,64 @@ body {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.pairing-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.pairing-modal-content {
|
||||
background-color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.pairing-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
background-color: #ccc;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@ -234,17 +234,17 @@ body {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
background-color: #f1f1f1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.message-container .message {
|
||||
align-self: flex-start;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.message-container .message.user {
|
||||
align-self: flex-end;
|
||||
margin-left: auto;
|
||||
align-self: flex-end;
|
||||
margin-left: auto;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@ -703,23 +703,23 @@ body {
|
||||
}
|
||||
|
||||
.new-request-btn {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.new-request-btn:hover {
|
||||
background-color: #45a049;
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
@ -762,7 +762,7 @@ body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
z-index: 1000;
|
||||
z-index: 1000;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
@ -816,7 +816,7 @@ body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
@ -912,12 +912,12 @@ body {
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
margin-left: 47%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
@ -1046,8 +1046,8 @@ body {
|
||||
|
||||
/* Style pour les labels */
|
||||
.form-group label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Style pour les champs de saisie */
|
||||
@ -1061,17 +1061,17 @@ body {
|
||||
}
|
||||
|
||||
.add-members-btn {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px; /* Espacement au-dessus du bouton */
|
||||
}
|
||||
|
||||
.add-members-btn:hover {
|
||||
background-color: #45a049;
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
/* Styles pour l'overlay et la modale */
|
||||
@ -1137,11 +1137,11 @@ body {
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #df2020;
|
||||
color: white;
|
||||
background-color: #df2020;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
BIN
sdk_client-0.1.3.tgz
Normal file
@ -9,7 +9,7 @@ let notifications = [];
|
||||
export async function unpair() {
|
||||
const service = await Services.getInstance();
|
||||
await service.unpairDevice();
|
||||
navigate('home');
|
||||
await navigate('home');
|
||||
}
|
||||
|
||||
(window as any).unpair = unpair;
|
||||
@ -28,7 +28,7 @@ function toggleMenu() {
|
||||
|
||||
async function getNotifications() {
|
||||
const service = await Services.getInstance();
|
||||
notifications = service.getNotifications();
|
||||
notifications = service.getNotifications() || [];
|
||||
return notifications;
|
||||
}
|
||||
function openCloseNotifications() {
|
||||
@ -102,8 +102,8 @@ async function setNotification(notifications: any[]): Promise<void> {
|
||||
|
||||
async function fetchNotifications() {
|
||||
const service = await Services.getInstance();
|
||||
const data = service.getNotifications();
|
||||
setNotification(data);
|
||||
const data = service.getNotifications() || [];
|
||||
await setNotification(data);
|
||||
}
|
||||
|
||||
async function loadUserProfile() {
|
||||
@ -204,7 +204,7 @@ async function disconnect() {
|
||||
await Promise.all(registrations.map(registration => registration.unregister()));
|
||||
console.log('Service worker unregistered');
|
||||
|
||||
navigate('home');
|
||||
await navigate('home');
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = window.location.origin;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import ModalService from '../../services/modal.service';
|
||||
|
||||
const modalService = await ModalService.getInstance();
|
||||
export async function confirm() {
|
||||
modalService.confirmPairing();
|
||||
}
|
||||
// export async function confirm() {
|
||||
// modalService.confirmPairing();
|
||||
// }
|
||||
|
||||
export async function closeConfirmationModal() {
|
||||
modalService.closeConfirmationModal();
|
||||
|
||||
@ -55,7 +55,7 @@ export default class QrScannerComponent extends HTMLElement {
|
||||
if (spAddress) {
|
||||
// Call the sendPairingTx function with the extracted sp_address
|
||||
try {
|
||||
await prepareAndSendPairingTx(spAddress);
|
||||
await prepareAndSendPairingTx();
|
||||
} catch (e) {
|
||||
console.error('Failed to pair:', e);
|
||||
}
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
<div id="validation-rule-modal" style="
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
">
|
||||
<div style="
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
width: 400px;
|
||||
max-width: 90%;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
">
|
||||
<h2 style="font-size: 1.2rem; font-weight: bold; margin-bottom: 1rem;">
|
||||
Add Validation Rule
|
||||
</h2>
|
||||
|
||||
<label style="display: block; margin-bottom: 0.5rem;">
|
||||
Quorum:
|
||||
<input id="vr-quorum" type="number" style="width: 100%; padding: 0.5rem; margin-top: 0.25rem;" />
|
||||
</label>
|
||||
|
||||
<label style="display: block; margin-bottom: 0.5rem;">
|
||||
Min Sig Member:
|
||||
<input id="vr-minsig" type="number" style="width: 100%; padding: 0.5rem; margin-top: 0.25rem;" />
|
||||
</label>
|
||||
|
||||
<label style="display: block; margin-bottom: 1rem;">
|
||||
Fields (comma-separated):
|
||||
<input id="vr-fields" type="text" placeholder="e.g. field1, field2" style="width: 100%; padding: 0.5rem; margin-top: 0.25rem;" />
|
||||
</label>
|
||||
|
||||
<div style="display: flex; justify-content: flex-end; gap: 1rem;">
|
||||
<button id="vr-cancel" style="padding: 0.5rem 1rem;">Cancel</button>
|
||||
<button id="vr-submit" style="padding: 0.5rem 1rem; background-color: #4f46e5; color: white; border: none; border-radius: 0.375rem;">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,61 @@
|
||||
export interface ValidationRule {
|
||||
quorum: number;
|
||||
fields: string[];
|
||||
min_sig_member: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and injects the modal HTML into the document if not already loaded.
|
||||
*/
|
||||
export async function loadValidationRuleModal(templatePath: string = '/src/components/validation-rule-modal/validation-rule-modal.html') {
|
||||
if (document.getElementById('validation-rule-modal')) return;
|
||||
|
||||
const res = await fetch(templatePath);
|
||||
const html = await res.text();
|
||||
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = html;
|
||||
|
||||
const modal = tempDiv.querySelector('#validation-rule-modal');
|
||||
if (!modal) {
|
||||
throw new Error('Modal HTML missing #validation-rule-modal');
|
||||
}
|
||||
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the modal and lets the user input a ValidationRule.
|
||||
* Calls the callback with the constructed rule on submit.
|
||||
*/
|
||||
export function showValidationRuleModal(onSubmit: (rule: ValidationRule) => void) {
|
||||
const modal = document.getElementById('validation-rule-modal')!;
|
||||
const quorumInput = document.getElementById('vr-quorum') as HTMLInputElement;
|
||||
const minsigInput = document.getElementById('vr-minsig') as HTMLInputElement;
|
||||
const fieldsInput = document.getElementById('vr-fields') as HTMLInputElement;
|
||||
|
||||
const cancelBtn = document.getElementById('vr-cancel')!;
|
||||
const submitBtn = document.getElementById('vr-submit')!;
|
||||
|
||||
// Reset values
|
||||
quorumInput.value = '';
|
||||
minsigInput.value = '';
|
||||
fieldsInput.value = '';
|
||||
|
||||
modal.style.display = 'flex';
|
||||
|
||||
cancelBtn.onclick = () => {
|
||||
modal.style.display = 'none';
|
||||
};
|
||||
|
||||
submitBtn.onclick = () => {
|
||||
const rule: ValidationRule = {
|
||||
quorum: parseInt(quorumInput.value),
|
||||
min_sig_member: parseInt(minsigInput.value),
|
||||
fields: fieldsInput.value.split(',').map(f => f.trim()).filter(Boolean),
|
||||
};
|
||||
|
||||
modal.style.display = 'none';
|
||||
onSubmit(rule);
|
||||
};
|
||||
}
|
||||
42
src/index.ts
@ -1,39 +1,3 @@
|
||||
// import Services from './services/service';
|
||||
|
||||
// document.addEventListener('DOMContentLoaded', async () => {
|
||||
// try {
|
||||
|
||||
// const services = await Services.getInstance();
|
||||
// setTimeout( async () => {
|
||||
// let device = await services.getDevice()
|
||||
// console.log("🚀 ~ setTimeout ~ device:", device)
|
||||
|
||||
// if(!device) {
|
||||
// device = await services.createNewDevice();
|
||||
// } else {
|
||||
// await services.restoreDevice(device)
|
||||
// }
|
||||
// await services.restoreProcesses();
|
||||
// await services.restoreMessages();
|
||||
|
||||
// const amount = await services.getAmount();
|
||||
|
||||
// if (amount === 0n) {
|
||||
// const faucetMsg = await services.createFaucetMessage();
|
||||
// await services.sendFaucetMessage(faucetMsg);
|
||||
// }
|
||||
// if (services.isPaired()) { await services.injectProcessListPage() }
|
||||
// else {
|
||||
// const queryString = window.location.search;
|
||||
// const urlParams = new URLSearchParams(queryString)
|
||||
// const pairingAddress = urlParams.get('sp_address')
|
||||
|
||||
// if(pairingAddress) {
|
||||
// setTimeout(async () => await services.sendPairingTx(pairingAddress), 2000)
|
||||
// }
|
||||
// }
|
||||
// }, 500);
|
||||
// } catch (error) {
|
||||
// console.error(error);
|
||||
// }
|
||||
// });
|
||||
export { default as Services } from './services/service';
|
||||
export { default as Database } from './services/database.service';
|
||||
export { MessageType } from './models/process.model';
|
||||
|
||||
14
src/main.ts
@ -1,18 +1,18 @@
|
||||
import { SignatureComponent } from './pages/signature/signature-component';
|
||||
import { SignatureElement } from './pages/signature/signature';
|
||||
import { ChatComponent } from './pages/chat/chat-component';
|
||||
import { ChatElement } from './pages/chat/chat';
|
||||
/*import { ChatComponent } from './pages/chat/chat-component';
|
||||
import { ChatElement } from './pages/chat/chat';*/
|
||||
import { AccountComponent } from './pages/account/account-component';
|
||||
import { AccountElement } from './pages/account/account';
|
||||
|
||||
export { SignatureComponent, SignatureElement, ChatComponent, ChatElement, AccountComponent, AccountElement };
|
||||
export { SignatureComponent, SignatureElement, AccountComponent, AccountElement };
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'signature-component': SignatureComponent;
|
||||
'signature-element': SignatureElement;
|
||||
'chat-component': ChatComponent;
|
||||
'chat-element': ChatElement;
|
||||
/*'chat-component': ChatComponent;
|
||||
'chat-element': ChatElement;*/
|
||||
'account-component': AccountComponent;
|
||||
'account-element': AccountElement;
|
||||
}
|
||||
@ -23,8 +23,8 @@ if ((import.meta as any).env.VITE_IS_INDEPENDANT_LIB) {
|
||||
// Initialiser les composants si nécessaire
|
||||
customElements.define('signature-component', SignatureComponent);
|
||||
customElements.define('signature-element', SignatureElement);
|
||||
customElements.define('chat-component', ChatComponent);
|
||||
customElements.define('chat-element', ChatElement);
|
||||
/*customElements.define('chat-component', ChatComponent);
|
||||
customElements.define('chat-element', ChatElement);*/
|
||||
customElements.define('account-component', AccountComponent);
|
||||
customElements.define('account-element', AccountElement);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Device, Process, SecretsStore } from "pkg/sdk_client";
|
||||
import { Device, Process, SecretsStore } from "../../pkg/sdk_client.js";
|
||||
|
||||
export interface BackUp {
|
||||
device: Device,
|
||||
|
||||
@ -21,3 +21,45 @@ export interface INotification {
|
||||
sendToNotificationPage?: boolean;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
export enum MessageType {
|
||||
// Establish connection and keep alive
|
||||
LISTENING = 'LISTENING',
|
||||
REQUEST_LINK = 'REQUEST_LINK',
|
||||
LINK_ACCEPTED = 'LINK_ACCEPTED',
|
||||
CREATE_PAIRING = 'CREATE_PAIRING',
|
||||
PAIRING_CREATED = 'PAIRING_CREATED',
|
||||
ERROR = 'ERROR',
|
||||
VALIDATE_TOKEN = 'VALIDATE_TOKEN',
|
||||
RENEW_TOKEN = 'RENEW_TOKEN',
|
||||
// Get various information
|
||||
GET_PAIRING_ID = 'GET_PAIRING_ID',
|
||||
GET_PROCESSES = 'GET_PROCESSES',
|
||||
GET_MY_PROCESSES = 'GET_MY_PROCESSES',
|
||||
PROCESSES_RETRIEVED = 'PROCESSES_RETRIEVED',
|
||||
RETRIEVE_DATA = 'RETRIEVE_DATA',
|
||||
DATA_RETRIEVED = 'DATA_RETRIEVED',
|
||||
DECODE_PUBLIC_DATA = 'DECODE_PUBLIC_DATA',
|
||||
PUBLIC_DATA_DECODED = 'PUBLIC_DATA_DECODED',
|
||||
GET_MEMBER_ADDRESSES = 'GET_MEMBER_ADDRESSES',
|
||||
MEMBER_ADDRESSES_RETRIEVED = 'MEMBER_ADDRESSES_RETRIEVED',
|
||||
// Processes
|
||||
CREATE_PROCESS = 'CREATE_PROCESS',
|
||||
PROCESS_CREATED = 'PROCESS_CREATED',
|
||||
UPDATE_PROCESS = 'UPDATE_PROCESS',
|
||||
PROCESS_UPDATED = 'PROCESS_UPDATED',
|
||||
NOTIFY_UPDATE = 'NOTIFY_UPDATE',
|
||||
UPDATE_NOTIFIED = 'UPDATE_NOTIFIED',
|
||||
VALIDATE_STATE = 'VALIDATE_STATE',
|
||||
STATE_VALIDATED = 'STATE_VALIDATED',
|
||||
// Hash and merkle proof
|
||||
HASH_VALUE = 'HASH_VALUE',
|
||||
VALUE_HASHED = 'VALUE_HASHED',
|
||||
GET_MERKLE_PROOF = 'GET_MERKLE_PROOF',
|
||||
MERKLE_PROOF_RETRIEVED = 'MERKLE_PROOF_RETRIEVED',
|
||||
VALIDATE_MERKLE_PROOF = 'VALIDATE_MERKLE_PROOF',
|
||||
MERKLE_PROOF_VALIDATED = 'MERKLE_PROOF_VALIDATED',
|
||||
// Account management
|
||||
ADD_DEVICE = 'ADD_DEVICE',
|
||||
DEVICE_ADDED = 'DEVICE_ADDED',
|
||||
}
|
||||
|
||||
@ -20,23 +20,25 @@ declare global {
|
||||
updateNavbarBanner: (bannerUrl: string) => void;
|
||||
saveBannerToLocalStorage: (bannerUrl: string) => void;
|
||||
loadSavedBanner: () => void;
|
||||
cancelAddRow: () => void;
|
||||
cancelAddRowPairing: () => void;
|
||||
saveName: (cell: HTMLElement, input: HTMLInputElement) => void;
|
||||
showProcessNotifications: (processName: string) => void;
|
||||
handleLogout: () => void;
|
||||
initializeEventListeners: () => void;
|
||||
showProcess: () => void;
|
||||
showProcessCreation: () => void;
|
||||
showDocumentValidation: () => void;
|
||||
updateNavbarName: (name: string) => void;
|
||||
updateNavbarLastName: (lastName: string) => void;
|
||||
showAlert: (title: string, text?: string, icon?: string) => void;
|
||||
addRow: () => void;
|
||||
confirmRow: () => void;
|
||||
cancelRow: () => void;
|
||||
deleteRow: (button: HTMLButtonElement) => void;
|
||||
addRowPairing: () => void;
|
||||
confirmRowPairing: () => void;
|
||||
cancelRowPairing: () => void;
|
||||
deleteRowPairing: (button: HTMLButtonElement) => void;
|
||||
generateRecoveryWords: () => string[];
|
||||
exportUserData: () => void;
|
||||
updateActionButtons: () => void;
|
||||
showQRCodeModal: (address: string) => void;
|
||||
showQRCodeModal: (pairingId: string) => void;
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,11 +49,31 @@ import { addressToEmoji } from '../../utils/sp-address.utils';
|
||||
import { getCorrectDOM } from '../../utils/document.utils';
|
||||
import accountStyle from '../../../public/style/account.css?inline';
|
||||
import Services from '../../services/service';
|
||||
import { getProcessCreation } from './process-creation';
|
||||
import { getDocumentValidation } from './document-validation';
|
||||
import { createProcessTab } from './process';
|
||||
|
||||
let isAddingRow = false;
|
||||
let currentRow: HTMLTableRowElement | null = null;
|
||||
let currentMode: keyof typeof STORAGE_KEYS = 'pairing';
|
||||
|
||||
interface Process {
|
||||
states: Array<{
|
||||
committed_in: string;
|
||||
keys: {};
|
||||
pcd_commitment: {
|
||||
counter: string;
|
||||
};
|
||||
public_data: {
|
||||
memberPublicName?: string;
|
||||
};
|
||||
roles: {
|
||||
pairing?: {};
|
||||
};
|
||||
state_id: string;
|
||||
validation_tokens: Array<any>;
|
||||
}>;
|
||||
}
|
||||
|
||||
class AccountElement extends HTMLElement {
|
||||
private dom: Node;
|
||||
@ -141,7 +163,7 @@ class AccountElement extends HTMLElement {
|
||||
<!-- User Info Section -->
|
||||
<div class="popup-info">
|
||||
<p><strong>Name:</strong> <span class="editable" id="popup-name"></span></p>
|
||||
<p><strong>Last Name:</strong> <span class="editable" id="popup-lastname"></span></p>
|
||||
<!--<p><strong>Last Name:</strong> <span class="editable" id="popup-lastname"></span></p>-->
|
||||
<p><strong>Address:</strong> 🏠 🌍 🗽🎊😩-🎊😑🎄😩</p>
|
||||
</div>
|
||||
|
||||
@ -154,27 +176,33 @@ class AccountElement extends HTMLElement {
|
||||
|
||||
</ul>
|
||||
<ul class="parameter-list-ul" onclick="window.showPairing()">Pairing 🔗</ul>
|
||||
<ul class="parameter-list-ul" onclick="window.showWallet()">Wallet 👛</ul>
|
||||
<!-- <ul class="parameter-list-ul" onclick="window.showWallet()">Wallet 👛</ul> -->
|
||||
<ul class="parameter-list-ul" onclick="window.showProcess()">Process ⚙️</ul>
|
||||
<ul class="parameter-list-ul" onclick="window.showData()">Data 💾</ul>
|
||||
<ul class="parameter-list-ul" onclick="window.showProcessCreation()">Process Creation</ul>
|
||||
<ul class="parameter-list-ul" onclick="window.showDocumentValidation()">Document Validation</ul>
|
||||
<!-- <ul class="parameter-list-ul" onclick="window.showData()">Data 💾</ul> -->
|
||||
</div>
|
||||
|
||||
<!-- Parameter Area -->
|
||||
<div class="parameter-area">
|
||||
<div class="content-container">
|
||||
<div id="pairing-content"></div>
|
||||
<div id="wallet-content"></div>
|
||||
<!-- <div id="wallet-content"></div> -->
|
||||
<div id="process-content"></div>
|
||||
<div id="data-content"></div>
|
||||
<div id="process-creation-content"></div>
|
||||
<div id="document-validation-content"></div>
|
||||
<!-- <div id="data-content"></div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
|
||||
window.showPairing = () => this.showPairing();
|
||||
window.showWallet = () => this.showWallet();
|
||||
window.showProcess = () => this.showProcess();
|
||||
window.showProcessCreation = () => this.showProcessCreation();
|
||||
window.showDocumentValidation = () => this.showDocumentValidation();
|
||||
window.showData = () => this.showData();
|
||||
window.addWalletRow = () => this.addWalletRow();
|
||||
window.confirmWalletRow = () => this.confirmWalletRow();
|
||||
@ -184,10 +212,10 @@ class AccountElement extends HTMLElement {
|
||||
window.handleLogout = () => this.handleLogout();
|
||||
window.confirmDeleteAccount = () => this.confirmDeleteAccount();
|
||||
window.showContractPopup = (contractId: string) => this.showContractPopup(contractId);
|
||||
window.addRow = () => this.addRow();
|
||||
window.deleteRow = (button: HTMLButtonElement) => this.deleteRow(button);
|
||||
window.confirmRow = () => this.confirmRow();
|
||||
window.cancelRow = () => this.cancelRow();
|
||||
window.addRowPairing = () => this.addRowPairing();
|
||||
window.deleteRowPairing = (button: HTMLButtonElement) => this.deleteRowPairing(button);
|
||||
window.confirmRowPairing = () => this.confirmRowPairing();
|
||||
window.cancelRowPairing = () => this.cancelRowPairing();
|
||||
window.updateNavbarBanner = (bannerUrl: string) => this.updateNavbarBanner(bannerUrl);
|
||||
window.saveBannerToLocalStorage = (bannerUrl: string) => this.saveBannerToLocalStorage(bannerUrl);
|
||||
window.loadSavedBanner = () => this.loadSavedBanner();
|
||||
@ -199,7 +227,7 @@ class AccountElement extends HTMLElement {
|
||||
window.updateActionButtons = () => this.updateActionButtons();
|
||||
window.openAvatarPopup = () => this.openAvatarPopup();
|
||||
window.closeAvatarPopup = () => this.closeAvatarPopup();
|
||||
window.showQRCodeModal = (address: string) => this.showQRCodeModal(address);
|
||||
window.showQRCodeModal = (pairingId: string) => this.showQRCodeModal(pairingId);
|
||||
|
||||
if (!localStorage.getItem('rows')) {
|
||||
localStorage.setItem('rows', JSON.stringify(defaultRows));
|
||||
@ -210,22 +238,22 @@ class AccountElement extends HTMLElement {
|
||||
this.initializeEventListeners();
|
||||
this.loadSavedBanner();
|
||||
this.loadUserInfo();
|
||||
|
||||
|
||||
const savedAvatar = localStorage.getItem('userAvatar');
|
||||
const savedBanner = localStorage.getItem('userBanner');
|
||||
const savedName = localStorage.getItem('userName');
|
||||
const savedLastName = localStorage.getItem('userLastName');
|
||||
|
||||
|
||||
if (savedAvatar) {
|
||||
const navAvatar = this.shadowRoot?.querySelector('.avatar') as HTMLImageElement;
|
||||
if (navAvatar) navAvatar.src = savedAvatar;
|
||||
}
|
||||
|
||||
|
||||
if (savedBanner) {
|
||||
const navBanner = this.shadowRoot?.querySelector('.banner-image') as HTMLImageElement;
|
||||
if (navBanner) navBanner.src = savedBanner;
|
||||
}
|
||||
|
||||
|
||||
if (savedName) {
|
||||
this.updateNavbarName(savedName);
|
||||
}
|
||||
@ -242,17 +270,17 @@ class AccountElement extends HTMLElement {
|
||||
alertPopup.className = 'alert-popup';
|
||||
this.shadowRoot?.appendChild(alertPopup);
|
||||
}
|
||||
|
||||
|
||||
// Définir le message et afficher la popup
|
||||
alertPopup.textContent = message;
|
||||
(alertPopup as HTMLElement).style.display = 'block';
|
||||
|
||||
|
||||
// Cacher la popup après 3 secondes
|
||||
setTimeout(() => {
|
||||
(alertPopup as HTMLElement).style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Fonctions de gestion des comptes et de l'interface utilisateur
|
||||
private confirmDeleteAccount(): void {
|
||||
@ -293,13 +321,13 @@ private updateNavbarBanner(imageUrl: string): void {
|
||||
if (!navbarSection) return;
|
||||
|
||||
let bannerImg = navbarSection.querySelector<HTMLImageElement>('.banner-image');
|
||||
|
||||
|
||||
if (!bannerImg) {
|
||||
bannerImg = document.createElement('img');
|
||||
bannerImg.className = 'banner-image';
|
||||
navbarSection.insertBefore(bannerImg, navbarSection.firstChild);
|
||||
}
|
||||
|
||||
|
||||
bannerImg.src = imageUrl;
|
||||
}
|
||||
|
||||
@ -340,7 +368,7 @@ private markAsRead(processName: string, messageId: number, element: HTMLElement)
|
||||
|
||||
element.classList.remove('unread');
|
||||
element.classList.add('read');
|
||||
|
||||
|
||||
const statusIcon = element.querySelector('.notification-status');
|
||||
if (statusIcon) {
|
||||
statusIcon.innerHTML = `
|
||||
@ -353,7 +381,7 @@ private markAsRead(processName: string, messageId: number, element: HTMLElement)
|
||||
const countElement = this.shadowRoot?.querySelector(`.notification-count[data-process="${processName}"]`);
|
||||
if (countElement) {
|
||||
countElement.textContent = `${notifCount.unread}/${notifCount.total}`;
|
||||
|
||||
|
||||
const bellContainer = countElement.closest('.notification-container');
|
||||
const bell = bellContainer?.querySelector('svg'); // Changé de .fa-bell à svg
|
||||
if (bell && bellContainer && notifCount.unread === 0) {
|
||||
@ -390,7 +418,7 @@ private exportRecovery(): void {
|
||||
if (result.isConfirmed) {
|
||||
const recoveryWords = this.generateRecoveryWords();
|
||||
localStorage.setItem('recoveryWords', JSON.stringify(recoveryWords));
|
||||
|
||||
|
||||
Swal.fire({
|
||||
title: 'Your Recovery Words',
|
||||
html: `
|
||||
@ -419,7 +447,7 @@ private exportRecovery(): void {
|
||||
if (result.isConfirmed) {
|
||||
// Stocker l'état du bouton dans le localStorage
|
||||
localStorage.setItem('recoveryExported', 'true');
|
||||
|
||||
|
||||
const exportRecoveryBtn = this.shadowRoot?.querySelector('.recovery-btn') as HTMLButtonElement;
|
||||
if (exportRecoveryBtn) {
|
||||
exportRecoveryBtn.disabled = true;
|
||||
@ -488,7 +516,7 @@ private getConfirmFunction(): string {
|
||||
case 'process':
|
||||
return 'window.confirmProcessRow()';
|
||||
default:
|
||||
return 'window.confirmRow()';
|
||||
return 'window.confirmRowPairing()';
|
||||
}
|
||||
}
|
||||
|
||||
@ -499,50 +527,94 @@ private getCancelFunction(): string {
|
||||
case 'process':
|
||||
return 'window.cancelProcessRow()';
|
||||
default:
|
||||
return 'window.cancelRow()';
|
||||
return 'window.cancelRowPairing()';
|
||||
}
|
||||
}
|
||||
|
||||
// Fonctions de gestion des tableaux
|
||||
private addRow(): void {
|
||||
private async addRowPairing(): Promise<void> {
|
||||
if (isAddingRow) return;
|
||||
|
||||
isAddingRow = true;
|
||||
const table = this.shadowRoot?.querySelector<HTMLTableElement>('#pairing-table tbody');
|
||||
if (!table) return;
|
||||
|
||||
currentRow = table.insertRow();
|
||||
const cells = ['SP Address', 'Device Name', 'SP Emojis'];
|
||||
|
||||
cells.forEach((_, index) => {
|
||||
const cell = currentRow!.insertCell();
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.className = 'edit-input';
|
||||
|
||||
// Ajouter un événement pour mettre à jour automatiquement les emojis
|
||||
if (index === 0) {
|
||||
input.addEventListener('input', async (e) => {
|
||||
const addressInput = e.target as HTMLInputElement;
|
||||
const emojiCell = currentRow!.cells[2];
|
||||
const emojis = await addressToEmoji(addressInput.value);
|
||||
if (emojiCell.querySelector('input')) {
|
||||
(emojiCell.querySelector('input') as HTMLInputElement).value = emojis;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (index === 2) {
|
||||
input.readOnly = true;
|
||||
}
|
||||
|
||||
cell.appendChild(input);
|
||||
// Créer la popup
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'pairing-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="pairing-modal-content">
|
||||
<h3>Add New Device</h3>
|
||||
<div class="pairing-form">
|
||||
<div class="form-group">
|
||||
<label for="sp-address">SP Address</label>
|
||||
<input type="text" id="sp-address" class="edit-input" placeholder="Enter SP Address">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="device-name">Device Name</label>
|
||||
<input type="text" id="device-name" class="edit-input" placeholder="Enter Device Name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sp-emojis">SP Emojis</label>
|
||||
<input type="text" id="sp-emojis" class="edit-input" readonly>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button class="confirm-button">Confirm</button>
|
||||
<button class="cancel-button">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.shadowRoot?.appendChild(modal);
|
||||
|
||||
// Ajouter les event listeners
|
||||
const spAddressInput = modal.querySelector('#sp-address') as HTMLInputElement;
|
||||
const spEmojisInput = modal.querySelector('#sp-emojis') as HTMLInputElement;
|
||||
const deviceNameInput = modal.querySelector('#device-name') as HTMLInputElement;
|
||||
const confirmButton = modal.querySelector('.confirm-button');
|
||||
const cancelButton = modal.querySelector('.cancel-button');
|
||||
|
||||
// Mettre à jour les emojis automatiquement
|
||||
spAddressInput?.addEventListener('input', async () => {
|
||||
const emojis = await addressToEmoji(spAddressInput.value);
|
||||
if (spEmojisInput) spEmojisInput.value = emojis;
|
||||
});
|
||||
|
||||
const deleteCell = currentRow.insertCell();
|
||||
deleteCell.style.width = '40px';
|
||||
// Gérer la confirmation
|
||||
confirmButton?.addEventListener('click', () => {
|
||||
const spAddress = spAddressInput?.value.trim();
|
||||
const deviceName = deviceNameInput?.value.trim();
|
||||
const spEmojis = spEmojisInput?.value.trim();
|
||||
|
||||
this.updateActionButtons();
|
||||
if (!spAddress || !deviceName) {
|
||||
this.showAlert('Please fill in all required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
//if (spAddress.length !== 118) {
|
||||
// this.showAlert('SP Address must be exactly 118 characters long');
|
||||
// return;
|
||||
//}
|
||||
|
||||
const newRow: Row = {
|
||||
column1: spAddress,
|
||||
column2: deviceName,
|
||||
column3: spEmojis || ''
|
||||
};
|
||||
|
||||
const storageKey = STORAGE_KEYS[currentMode];
|
||||
const rows: Row[] = JSON.parse(localStorage.getItem(storageKey) || '[]');
|
||||
rows.push(newRow);
|
||||
localStorage.setItem(storageKey, JSON.stringify(rows));
|
||||
|
||||
this.updateTableContent(rows);
|
||||
modal.remove();
|
||||
isAddingRow = false;
|
||||
});
|
||||
|
||||
// Gérer l'annulation
|
||||
cancelButton?.addEventListener('click', () => {
|
||||
modal.remove();
|
||||
isAddingRow = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Fonctions de mise à jour de l'interface
|
||||
@ -552,18 +624,17 @@ private updateTableContent(rows: Row[]): void {
|
||||
|
||||
tbody.innerHTML = rows.map(row => `
|
||||
<tr>
|
||||
<td>${row.column1}</td>
|
||||
<td class="device-name" onclick="window.editDeviceName(this)">${row.column2}</td>
|
||||
<td>${row.column3}</td>
|
||||
<td>
|
||||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=50x50&data=${encodeURIComponent(row.column1)}"
|
||||
alt="QR Code"
|
||||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=50x50&data=${encodeURIComponent(row.column1)}"
|
||||
alt="QR Code"
|
||||
title="${row.column1}"
|
||||
class="qr-code"
|
||||
onclick="window.showQRCodeModal('${encodeURIComponent(row.column1)}')">
|
||||
</td>
|
||||
<td>
|
||||
<button class="delete-button" onclick="window.deleteRow(this)">
|
||||
<button class="delete-button" onclick="window.deleteRowPairing(this)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="red">
|
||||
<path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/>
|
||||
</svg>
|
||||
@ -575,7 +646,7 @@ private updateTableContent(rows: Row[]): void {
|
||||
|
||||
|
||||
|
||||
private confirmRow(): void {
|
||||
private confirmRowPairing(): void {
|
||||
if (!currentRow) return;
|
||||
|
||||
const inputs = currentRow.getElementsByTagName('input');
|
||||
@ -606,18 +677,18 @@ private confirmRow(): void {
|
||||
|
||||
isAddingRow = false;
|
||||
currentRow = null;
|
||||
|
||||
|
||||
this.resetButtonContainer();
|
||||
this.updateTableContent(rows);
|
||||
}
|
||||
|
||||
private cancelRow(): void {
|
||||
private cancelRowPairing(): void {
|
||||
if (!currentRow) return;
|
||||
|
||||
|
||||
currentRow.remove();
|
||||
isAddingRow = false;
|
||||
currentRow = null;
|
||||
|
||||
|
||||
this.resetButtonContainer();
|
||||
}
|
||||
|
||||
@ -626,11 +697,11 @@ private resetButtonContainer(): void {
|
||||
if (!buttonContainer) return;
|
||||
|
||||
buttonContainer.innerHTML = `
|
||||
<button class="add-row-button button-style" onclick="window.addRow()">Add a line</button>
|
||||
<button class="add-row-button button-style" onclick="window.addRowPairing()">Add a line</button>
|
||||
`;
|
||||
}
|
||||
|
||||
private deleteRow(button: HTMLButtonElement): void {
|
||||
private deleteRowPairing(button: HTMLButtonElement): void {
|
||||
const row = button.closest('tr');
|
||||
if (!row) return;
|
||||
|
||||
@ -643,21 +714,53 @@ private deleteRow(button: HTMLButtonElement): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = Array.from(table.children).indexOf(row);
|
||||
row.style.transition = 'opacity 0.3s, transform 0.3s';
|
||||
row.style.opacity = '0';
|
||||
row.style.transform = 'translateX(-100%)';
|
||||
// Créer la modal de confirmation
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'confirm-delete-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="confirm-delete-content">
|
||||
<h3>Confirm Deletion</h3>
|
||||
<p>Are you sure you want to delete this device?</p>
|
||||
<div class="confirm-delete-buttons">
|
||||
<button class="cancel-btn">Cancel</button>
|
||||
<button class="confirm-btn">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
setTimeout(() => {
|
||||
row.remove();
|
||||
this.shadowRoot?.appendChild(modal);
|
||||
|
||||
// Gérer les boutons de la modal
|
||||
const confirmBtn = modal.querySelector('.confirm-btn');
|
||||
const cancelBtn = modal.querySelector('.cancel-btn');
|
||||
|
||||
confirmBtn?.addEventListener('click', () => {
|
||||
// Calculer l'index AVANT de supprimer la ligne du DOM
|
||||
const index = Array.from(table.children).indexOf(row);
|
||||
const storageKey = STORAGE_KEYS[currentMode];
|
||||
const rows = JSON.parse(localStorage.getItem(storageKey) || '[]');
|
||||
|
||||
// Supprimer du localStorage
|
||||
if (index > -1) {
|
||||
rows.splice(index, 1);
|
||||
localStorage.setItem(storageKey, JSON.stringify(rows));
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// Animation et suppression du DOM
|
||||
row.style.transition = 'opacity 0.3s, transform 0.3s';
|
||||
row.style.opacity = '0';
|
||||
row.style.transform = 'translateX(-100%)';
|
||||
|
||||
setTimeout(() => {
|
||||
row.remove();
|
||||
}, 300);
|
||||
|
||||
modal.remove();
|
||||
});
|
||||
|
||||
cancelBtn?.addEventListener('click', () => {
|
||||
modal.remove();
|
||||
});
|
||||
}
|
||||
|
||||
private editDeviceName(cell: HTMLTableCellElement): void {
|
||||
@ -668,7 +771,7 @@ private editDeviceName(cell: HTMLTableCellElement): void {
|
||||
input.type = 'text';
|
||||
input.value = currentValue;
|
||||
input.className = 'edit-input';
|
||||
|
||||
|
||||
input.addEventListener('blur', () => this.finishEditing(cell, input));
|
||||
input.addEventListener('keypress', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
@ -682,7 +785,7 @@ private editDeviceName(cell: HTMLTableCellElement): void {
|
||||
input.focus();
|
||||
}
|
||||
|
||||
private finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): void {
|
||||
private async finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): Promise<void> {
|
||||
const newValue = input.value.trim();
|
||||
if (newValue === '') {
|
||||
cell.textContent = cell.getAttribute('data-original-value') || '';
|
||||
@ -690,102 +793,103 @@ private finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): void
|
||||
return;
|
||||
}
|
||||
|
||||
const row = cell.closest('tr');
|
||||
if (!row) return;
|
||||
try {
|
||||
const service = await Services.getInstance();
|
||||
const pairingProcessId = service.getPairingProcessId();
|
||||
const process = await service.getProcess(pairingProcessId);
|
||||
if (!process) throw new Error('Pairing process not found');
|
||||
// Mettre à jour le nom via le service
|
||||
await service.updateMemberPublicName(process, newValue);
|
||||
|
||||
const table = row.closest('tbody');
|
||||
if (!table) return;
|
||||
|
||||
const index = Array.from(table.children).indexOf(row);
|
||||
const storageKey = STORAGE_KEYS[currentMode];
|
||||
const rows: Row[] = JSON.parse(localStorage.getItem(storageKey) || '[]');
|
||||
|
||||
if (rows[index]) {
|
||||
rows[index].column2 = newValue;
|
||||
localStorage.setItem(storageKey, JSON.stringify(rows));
|
||||
// Mettre à jour l'interface
|
||||
cell.textContent = newValue;
|
||||
cell.classList.remove('editing');
|
||||
} catch (error) {
|
||||
console.error('Failed to update name:', error);
|
||||
// Restaurer l'ancienne valeur en cas d'erreur
|
||||
cell.textContent = cell.getAttribute('data-original-value') || '';
|
||||
cell.classList.remove('editing');
|
||||
}
|
||||
|
||||
cell.textContent = newValue;
|
||||
cell.classList.remove('editing');
|
||||
}
|
||||
|
||||
// Fonction pour gérer le téléchargement de l'avatar
|
||||
private handleAvatarUpload(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
|
||||
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e: ProgressEvent<FileReader>) => {
|
||||
const result = e.target?.result as string;
|
||||
const popupAvatar = this.shadowRoot?.getElementById('popup-avatar-img') as HTMLImageElement;
|
||||
const navAvatar = this.shadowRoot?.querySelector('.nav-wrapper .avatar') as HTMLImageElement;
|
||||
|
||||
|
||||
if (popupAvatar) popupAvatar.src = result;
|
||||
if (navAvatar) navAvatar.src = result;
|
||||
|
||||
|
||||
localStorage.setItem('userAvatar', result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private showProcess(): void {
|
||||
//console.log("showProcess called");
|
||||
currentMode = 'process';
|
||||
private async showProcessCreation(): Promise<void> {
|
||||
this.hideAllContent();
|
||||
|
||||
const headerTitle = this.shadowRoot?.getElementById('header-title');
|
||||
if (headerTitle) headerTitle.textContent = 'Process';
|
||||
|
||||
const processContent = this.shadowRoot?.getElementById('process-content');
|
||||
if (processContent) {
|
||||
processContent.style.display = 'block';
|
||||
processContent.innerHTML = `
|
||||
<div class="parameter-header" id="parameter-header">Process</div>
|
||||
<div class="table-container">
|
||||
<table class="parameter-table" id="process-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Name</th>
|
||||
<th>Role</th>
|
||||
<th>Notifications</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
this.updateProcessTableContent(mockProcessRows);
|
||||
const container = this.shadowRoot?.getElementById('process-creation-content');
|
||||
if (container) {
|
||||
getProcessCreation(container);
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction utilitaire pour mettre à jour le contenu du tableau Process
|
||||
private updateProcessTableContent(rows: any[]): void {
|
||||
const tbody = this.shadowRoot?.querySelector('#process-table tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
tbody.innerHTML = rows.map(row => `
|
||||
<tr>
|
||||
<td>${row.process}</td>
|
||||
<td>${row.role}</td>
|
||||
<td>
|
||||
<div class="notification-container" onclick="window.showProcessNotifications('${row.process}')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16">
|
||||
<path d="M224 0c-17.7 0-32 14.3-32 32V51.2C119 66 64 130.6 64 208v18.8c0 47-17.3 92.4-48.5 127.6l-7.4 8.3c-8.4 9.4-10.4 22.9-5.3 34.4S19.4 416 32 416H416c12.6 0 24-7.4 29.2-18.9s3.1-25-5.3-34.4l-7.4-8.3C401.3 319.2 384 273.9 384 226.8V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32zm45.3 493.3c12-12 18.7-28.3 18.7-45.3H160c0 17 6.7 33.3 18.7 45.3s28.3 18.7 45.3 18.7s33.3-6.7 45.3-18.7z"/>
|
||||
</svg>
|
||||
<span class="notification-count" data-process="${row.process}">
|
||||
${row.notification?.messages?.filter((m: any) => !m.read).length || 0}/${row.notification?.messages?.length || 0}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
private async showDocumentValidation(): Promise<void> {
|
||||
this.hideAllContent();
|
||||
const container = this.shadowRoot?.getElementById('document-validation-content');
|
||||
if (container) {
|
||||
getDocumentValidation(container);
|
||||
}
|
||||
}
|
||||
|
||||
private async showProcess(): Promise<void> {
|
||||
this.hideAllContent();
|
||||
const container = this.shadowRoot?.getElementById('process-content');
|
||||
if (container) {
|
||||
const service = await Services.getInstance();
|
||||
const myProcesses = await service.getMyProcesses();
|
||||
if (myProcesses && myProcesses.length != 0) {
|
||||
const myProcessesDataUnfiltered = await Promise.all(myProcesses.map(async processId => {
|
||||
const process = await service.getProcess(processId);
|
||||
if (!process) return undefined;
|
||||
const lastState = service.getLastCommitedState(process);
|
||||
if (!lastState) {
|
||||
return {
|
||||
name: '',
|
||||
publicData: {}
|
||||
};
|
||||
}
|
||||
const description = await service.decryptAttribute(processId, lastState, 'description');
|
||||
const name = description ? description : 'N/A';
|
||||
const publicData = await service.getPublicData(process);
|
||||
if (!publicData) {
|
||||
return {
|
||||
name: '',
|
||||
publicData: {}
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
publicData: publicData
|
||||
};
|
||||
}));
|
||||
const myProcessesData = (myProcessesDataUnfiltered.filter(
|
||||
(p) => p && p.name !== '' && Object.keys(p.publicData).length != 0
|
||||
)) as { name: string, publicData: Record<string, any> }[];
|
||||
|
||||
createProcessTab(container, myProcessesData);
|
||||
} else {
|
||||
createProcessTab(container, []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private showProcessNotifications(processName: string): void {
|
||||
const process = mockProcessRows.find(p => p.process === processName);
|
||||
@ -793,15 +897,15 @@ private showProcessNotifications(processName: string): void {
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'notifications-modal';
|
||||
|
||||
|
||||
let notificationsList = process.notification.messages.map(msg => `
|
||||
<div class="notification-item ${msg.read ? 'read' : 'unread'}"
|
||||
<div class="notification-item ${msg.read ? 'read' : 'unread'}"
|
||||
onclick="window.markAsRead('${processName}', ${msg.id}, this)">
|
||||
<div class="notification-status">
|
||||
${msg.read ?
|
||||
${msg.read ?
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="green">
|
||||
<path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/>
|
||||
</svg>` :
|
||||
</svg>` :
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16" fill="black">
|
||||
<path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512z"/>
|
||||
</svg>`
|
||||
@ -852,9 +956,11 @@ private handleLogout(): void {
|
||||
|
||||
|
||||
// Fonctions de gestion des contrats
|
||||
private showContractPopup(contractId: string) {
|
||||
// Empêcher la navigation par défaut
|
||||
event?.preventDefault();
|
||||
private showContractPopup(contractId: string, event?: Event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Check if the contract exists in mockContracts
|
||||
const contract = mockContracts[contractId as keyof typeof mockContracts];
|
||||
if (!contract) {
|
||||
@ -862,7 +968,6 @@ private showContractPopup(contractId: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Créer la popup
|
||||
const popup = document.createElement('div');
|
||||
popup.className = 'contract-popup-overlay';
|
||||
popup.innerHTML = `
|
||||
@ -881,13 +986,11 @@ private showContractPopup(contractId: string) {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Ajouter la popup au body
|
||||
this.shadowRoot?.appendChild(popup);
|
||||
|
||||
// Gérer la fermeture
|
||||
const closeBtn = popup.querySelector('.close-contract-popup');
|
||||
const closePopup = () => popup.remove();
|
||||
|
||||
|
||||
closeBtn?.addEventListener('click', closePopup);
|
||||
popup.addEventListener('click', (e) => {
|
||||
if (e.target === popup) closePopup();
|
||||
@ -896,7 +999,7 @@ private showContractPopup(contractId: string) {
|
||||
|
||||
// Fonction utilitaire pour cacher tous les contenus
|
||||
private hideAllContent(): void {
|
||||
const contents = ['pairing-content', 'wallet-content', 'process-content', 'data-content'];
|
||||
const contents = ['pairing-content', 'wallet-content', 'process-content', 'process-creation-content', 'data-content', 'document-validation-content'];
|
||||
contents.forEach(id => {
|
||||
const element = this.shadowRoot?.getElementById(id);
|
||||
if (element) {
|
||||
@ -909,13 +1012,13 @@ private hideAllContent(): void {
|
||||
private async showPairing(): Promise<void> {
|
||||
const service = await Services.getInstance();
|
||||
const spAddress = await service.getDeviceAddress();
|
||||
|
||||
|
||||
isAddingRow = false;
|
||||
currentRow = null;
|
||||
currentMode = 'pairing';
|
||||
|
||||
|
||||
this.hideAllContent();
|
||||
|
||||
|
||||
const headerElement = this.shadowRoot?.getElementById('parameter-header');
|
||||
if (headerElement) {
|
||||
headerElement.textContent = 'Pairing';
|
||||
@ -930,7 +1033,6 @@ private async showPairing(): Promise<void> {
|
||||
<table class="parameter-table" id="pairing-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>SP Address</th>
|
||||
<th>Device Name</th>
|
||||
<th>SP Emojis</th>
|
||||
<th>QR Code</th>
|
||||
@ -940,30 +1042,29 @@ private async showPairing(): Promise<void> {
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<div class="button-container">
|
||||
<button class="add-row-button button-style" onclick="window.addRow()">Add a line</button>
|
||||
<button class="add-row-button button-style" onclick="window.addRowPairing()">Add a device</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.pairing) || '[]');
|
||||
|
||||
|
||||
const deviceExists = rows.some((row: Row) => row.column1 === spAddress);
|
||||
|
||||
|
||||
if (!deviceExists && spAddress) {
|
||||
const emojis = await addressToEmoji(spAddress);
|
||||
|
||||
|
||||
try {
|
||||
// Déboguer le processus de pairing
|
||||
const pairingProcessId = await service.getPairingProcessId();
|
||||
console.log('Pairing Process ID:', pairingProcessId);
|
||||
|
||||
|
||||
const pairingProcess = await service.getProcess(pairingProcessId);
|
||||
console.log('Pairing Process:', pairingProcess);
|
||||
|
||||
const userName = pairingProcess?.states?.[0]?.metadata?.userName
|
||||
|| pairingProcess?.states?.[0]?.metadata?.name
|
||||
|
||||
const userName = pairingProcess?.states?.[0]?.public_data?.memberPublicName
|
||||
|| localStorage.getItem('userName')
|
||||
|
||||
|
||||
console.log('Username found:', userName);
|
||||
|
||||
const newRow = {
|
||||
@ -984,7 +1085,7 @@ private async showPairing(): Promise<void> {
|
||||
localStorage.setItem(STORAGE_KEYS.pairing, JSON.stringify(rows));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.updateTableContent(rows);
|
||||
}
|
||||
}
|
||||
@ -995,11 +1096,11 @@ private showWallet(): void {
|
||||
|
||||
currentMode = 'wallet';
|
||||
this.hideAllContent();
|
||||
|
||||
|
||||
// Mettre à jour le titre
|
||||
const headerTitle = this.shadowRoot?.getElementById('header-title');
|
||||
if (headerTitle) headerTitle.textContent = 'Wallet';
|
||||
|
||||
|
||||
const walletContent = this.shadowRoot?.getElementById('wallet-content');
|
||||
if (!walletContent) return;
|
||||
walletContent.style.display = 'block';
|
||||
@ -1021,7 +1122,7 @@ private showWallet(): void {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || '[]');
|
||||
this.updateWalletTableContent(rows);
|
||||
}
|
||||
@ -1044,10 +1145,10 @@ private showData(): void {
|
||||
//console.log("showData called");
|
||||
currentMode = 'data';
|
||||
this.hideAllContent();
|
||||
|
||||
|
||||
const headerTitle = this.shadowRoot?.getElementById('header-title');
|
||||
if (headerTitle) headerTitle.textContent = 'Data';
|
||||
|
||||
|
||||
const dataContent = this.shadowRoot?.getElementById('data-content');
|
||||
if (dataContent) {
|
||||
dataContent.style.display = 'block';
|
||||
@ -1069,7 +1170,7 @@ private showData(): void {
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
const rows = mockDataRows || JSON.parse(localStorage.getItem(STORAGE_KEYS.data) || '[]');
|
||||
this.updateDataTableContent(rows);
|
||||
}
|
||||
@ -1098,7 +1199,7 @@ private addWalletRow(): void {
|
||||
// Remplacer le bouton "Add a line" par les boutons de confirmation/annulation
|
||||
const buttonContainer = this.shadowRoot?.querySelector('#wallet-content .button-container');
|
||||
if (!buttonContainer) return;
|
||||
|
||||
|
||||
buttonContainer.innerHTML = `
|
||||
<div class="action-buttons-wrapper">
|
||||
<button onclick="confirmWalletRow()" class="action-button confirm-button">✓</button>
|
||||
@ -1136,20 +1237,20 @@ private confirmWalletRow(): void {
|
||||
|
||||
private cancelWalletRow(): void {
|
||||
if (!currentRow) return;
|
||||
|
||||
|
||||
currentRow.remove();
|
||||
isAddingRow = false;
|
||||
currentRow = null;
|
||||
|
||||
|
||||
// Réinitialiser le conteneur de boutons avec le bouton "Add a line"
|
||||
const buttonContainer = this.shadowRoot?.querySelector('#wallet-content .button-container');
|
||||
if (!buttonContainer) return;
|
||||
|
||||
|
||||
buttonContainer.innerHTML = `
|
||||
<button class="add-row-button button-style" onclick="window.addWalletRow()">Add a line</button>
|
||||
`;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private updateDataTableContent(rows: DataRow[]): void {
|
||||
@ -1209,16 +1310,16 @@ private openAvatarPopup(): void {
|
||||
<strong>Name:</strong>
|
||||
<input type="text" id="userName" value="${savedName}" class="editable">
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<!--<div class="info-row">
|
||||
<strong>Last Name:</strong>
|
||||
<input type="text" id="userLastName" value="${savedLastName}" class="editable">
|
||||
</div>
|
||||
</div>-->
|
||||
<div class="info-row">
|
||||
<strong>Address:</strong>
|
||||
<span>${savedAddress}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="popup-button-container">
|
||||
<div class="action-buttons-row">
|
||||
<button class="export-btn" onclick="window.exportUserData()">Export User Data</button>
|
||||
@ -1236,7 +1337,7 @@ private openAvatarPopup(): void {
|
||||
// Ajouter le gestionnaire d'événements pour la bannière
|
||||
const bannerImg = popup.querySelector('#popup-banner-img');
|
||||
const bannerInput = popup.querySelector('#banner-upload') as HTMLInputElement;
|
||||
|
||||
|
||||
if (bannerImg && bannerInput) {
|
||||
bannerImg.addEventListener('click', () => {
|
||||
bannerInput.click();
|
||||
@ -1275,10 +1376,10 @@ private setupEventListeners(popup: HTMLElement): void {
|
||||
// Mise à jour de l'avatar dans la preview et le popup
|
||||
const popupAvatar = this.shadowRoot?.getElementById('popup-avatar-img') as HTMLImageElement;
|
||||
const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement;
|
||||
|
||||
|
||||
if (popupAvatar) popupAvatar.src = result;
|
||||
if (previewAvatar) previewAvatar.src = result;
|
||||
|
||||
|
||||
localStorage.setItem('userAvatar', result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
@ -1298,10 +1399,10 @@ private setupEventListeners(popup: HTMLElement): void {
|
||||
// Mise à jour de la bannière dans la preview et le popup
|
||||
const popupBanner = this.shadowRoot?.getElementById('popup-banner-img') as HTMLImageElement;
|
||||
const previewBanner = this.shadowRoot?.querySelector('.preview-banner-img') as HTMLImageElement;
|
||||
|
||||
|
||||
if (popupBanner) popupBanner.src = result;
|
||||
if (previewBanner) previewBanner.src = result;
|
||||
|
||||
|
||||
localStorage.setItem('userBanner', result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
@ -1354,7 +1455,7 @@ private loadUserInfo(): void {
|
||||
const savedLastName = localStorage.getItem('userLastName');
|
||||
const savedAvatar = localStorage.getItem('userAvatar');
|
||||
const savedBanner = localStorage.getItem('userBanner');
|
||||
|
||||
|
||||
// Mise à jour du nom dans la preview
|
||||
if (savedName) {
|
||||
const previewName = this.shadowRoot?.querySelector('.preview-name');
|
||||
@ -1370,7 +1471,7 @@ private loadUserInfo(): void {
|
||||
previewLastName.textContent = savedLastName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Mise à jour de l'avatar dans la preview
|
||||
if (savedAvatar) {
|
||||
const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement;
|
||||
@ -1378,7 +1479,7 @@ private loadUserInfo(): void {
|
||||
previewAvatar.src = savedAvatar;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Mise à jour de la bannière dans la preview
|
||||
if (savedBanner) {
|
||||
const previewBanner = this.shadowRoot?.querySelector('.preview-banner-img') as HTMLImageElement;
|
||||
@ -1402,11 +1503,11 @@ private updateNavbarLastName(lastName: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
private updateProfilePreview(data: {
|
||||
avatar?: string,
|
||||
banner?: string,
|
||||
name?: string,
|
||||
lastName?: string
|
||||
private updateProfilePreview(data: {
|
||||
avatar?: string,
|
||||
banner?: string,
|
||||
name?: string,
|
||||
lastName?: string
|
||||
}): void {
|
||||
if (data.avatar) {
|
||||
const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement;
|
||||
@ -1444,7 +1545,7 @@ private initializeEventListeners() {
|
||||
input.type = 'text';
|
||||
input.value = currentValue;
|
||||
input.className = 'edit-input';
|
||||
|
||||
|
||||
field.textContent = '';
|
||||
field.appendChild(input);
|
||||
field.classList.add('editing');
|
||||
@ -1460,16 +1561,16 @@ private initializeEventListeners() {
|
||||
}
|
||||
}
|
||||
|
||||
private showQRCodeModal(address: string): void {
|
||||
private showQRCodeModal(pairingId: string): void {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'qr-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="qr-modal-content">
|
||||
<span class="close-qr-modal">×</span>
|
||||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${address}"
|
||||
alt="QR Code Large"
|
||||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${pairingId}"
|
||||
alt="QR Code Large"
|
||||
class="qr-code-large">
|
||||
<div class="qr-address">${decodeURIComponent(address)}</div>
|
||||
<div class="qr-address">${decodeURIComponent(pairingId)}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
321
src/pages/account/document-validation.ts
Normal file
@ -0,0 +1,321 @@
|
||||
import { ProcessState } from '../../../pkg/sdk_client.js';
|
||||
import Services from '../../services/service.js';
|
||||
|
||||
interface State {
|
||||
file: File | null;
|
||||
fileHash: string | null;
|
||||
certificate: ProcessState | null;
|
||||
commitmentHashes: string[];
|
||||
}
|
||||
|
||||
export interface Vin {
|
||||
txid: string; // The txid of the previous transaction (being spent)
|
||||
vout: number; // The output index in the previous tx
|
||||
prevout: {
|
||||
scriptpubkey: string;
|
||||
scriptpubkey_asm: string;
|
||||
scriptpubkey_type: string;
|
||||
scriptpubkey_address: string;
|
||||
value: number;
|
||||
};
|
||||
scriptsig: string;
|
||||
scriptsig_asm: string;
|
||||
witness: string[];
|
||||
is_coinbase: boolean;
|
||||
sequence: number;
|
||||
}
|
||||
|
||||
export interface TransactionInfo {
|
||||
txid: string;
|
||||
version: number;
|
||||
locktime: number;
|
||||
vin: Vin[];
|
||||
vout: any[];
|
||||
size: number;
|
||||
weight: number;
|
||||
fee: number;
|
||||
status: {
|
||||
confirmed: boolean;
|
||||
block_height: number;
|
||||
block_hash: string;
|
||||
block_time: number;
|
||||
};
|
||||
}
|
||||
|
||||
export function getDocumentValidation(container: HTMLElement) {
|
||||
const state: State = {
|
||||
file: null,
|
||||
fileHash: null,
|
||||
certificate: null,
|
||||
commitmentHashes: []
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
container.style.cssText = `
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
gap: 2rem;
|
||||
`;
|
||||
|
||||
function createDropButton(
|
||||
label: string,
|
||||
onDrop: (file: File, updateVisuals: (file: File) => void) => void,
|
||||
accept: string = '*/*'
|
||||
): HTMLElement {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.style.cssText = `
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
border: 2px dashed #888;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
background: #f8f8f8;
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const title = document.createElement('div');
|
||||
title.textContent = label;
|
||||
|
||||
const filename = document.createElement('div');
|
||||
filename.style.cssText = `
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.5rem;
|
||||
color: #444;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
wrapper.appendChild(title);
|
||||
wrapper.appendChild(filename);
|
||||
|
||||
const updateVisuals = (file: File) => {
|
||||
wrapper.style.borderColor = 'green';
|
||||
wrapper.style.background = '#e6ffed';
|
||||
filename.textContent = file.name;
|
||||
};
|
||||
|
||||
// === Hidden file input ===
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = accept;
|
||||
fileInput.style.display = 'none';
|
||||
document.body.appendChild(fileInput);
|
||||
|
||||
fileInput.onchange = () => {
|
||||
const file = fileInput.files?.[0];
|
||||
if (file) {
|
||||
onDrop(file, updateVisuals);
|
||||
fileInput.value = ''; // reset so same file can be re-selected
|
||||
}
|
||||
};
|
||||
|
||||
// === Handle drag-and-drop ===
|
||||
wrapper.ondragover = e => {
|
||||
e.preventDefault();
|
||||
wrapper.style.background = '#e0e0e0';
|
||||
};
|
||||
|
||||
wrapper.ondragleave = () => {
|
||||
wrapper.style.background = '#f8f8f8';
|
||||
};
|
||||
|
||||
wrapper.ondrop = e => {
|
||||
e.preventDefault();
|
||||
wrapper.style.background = '#f8f8f8';
|
||||
|
||||
const file = e.dataTransfer?.files?.[0];
|
||||
if (file) {
|
||||
onDrop(file, updateVisuals);
|
||||
}
|
||||
};
|
||||
|
||||
// === Handle click to open file manager ===
|
||||
wrapper.onclick = () => {
|
||||
fileInput.click();
|
||||
};
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
const fileDropButton = createDropButton('Drop file', async (file, updateVisuals) => {
|
||||
try {
|
||||
state.file = file;
|
||||
updateVisuals(file);
|
||||
console.log('Loaded file:', state.file);
|
||||
checkReady();
|
||||
} catch (err) {
|
||||
alert('Failed to drop the file.');
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
const certDropButton = createDropButton('Drop certificate', async (file, updateVisuals) => {
|
||||
try {
|
||||
const text = await file.text();
|
||||
const json = JSON.parse(text);
|
||||
if (
|
||||
typeof json === 'object' &&
|
||||
json !== null &&
|
||||
typeof json.pcd_commitment === 'object' &&
|
||||
typeof json.state_id === 'string'
|
||||
) {
|
||||
state.certificate = json as ProcessState;
|
||||
|
||||
state.commitmentHashes = Object.values(json.pcd_commitment).map((value: unknown, _idx: number, _arr: unknown[]) =>
|
||||
String(value).toLowerCase()
|
||||
);
|
||||
|
||||
updateVisuals(file);
|
||||
console.log('Loaded certificate, extracted hashes:', state.commitmentHashes);
|
||||
checkReady();
|
||||
} else {
|
||||
alert('Invalid certificate structure.');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Failed to parse certificate JSON.');
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
const buttonRow = document.createElement('div');
|
||||
buttonRow.style.display = 'flex';
|
||||
buttonRow.style.gap = '2rem';
|
||||
buttonRow.appendChild(fileDropButton);
|
||||
buttonRow.appendChild(certDropButton);
|
||||
|
||||
container.appendChild(buttonRow);
|
||||
|
||||
async function checkReady() {
|
||||
if (state.file && state.certificate && state.commitmentHashes.length > 0) {
|
||||
// We take the commited_in and all pcd_commitment keys to reconstruct all the possible hash
|
||||
const fileBlob = {
|
||||
type: state.file.type,
|
||||
data: new Uint8Array(await state.file.arrayBuffer())
|
||||
};
|
||||
const service = await Services.getInstance();
|
||||
const commitedIn = state.certificate.commited_in;
|
||||
if (!commitedIn) return;
|
||||
const [prevTxid, prevTxVout] = commitedIn.split(':');
|
||||
const processId = (state.certificate as any).process_id as string;
|
||||
const stateId = state.certificate.state_id;
|
||||
const process = await service.getProcess(processId);
|
||||
if (!process) return;
|
||||
|
||||
// Get the transaction that comes right after the commited_in
|
||||
const nextState = service.getNextStateAfterId(process, stateId);
|
||||
|
||||
if (!nextState) {
|
||||
alert(`❌ Validation failed: No next state, is the state you're trying to validate commited?`);
|
||||
return;
|
||||
}
|
||||
|
||||
const [outspentTxId, _] = nextState.commited_in.split(':');
|
||||
console.log(outspentTxId);
|
||||
|
||||
// Check that the commitment transaction exists, and that it commits to the state id
|
||||
|
||||
const txInfo = await fetchTransaction(outspentTxId);
|
||||
if (!txInfo) {
|
||||
console.error(`Validation error: Can't fetch new state commitment transaction`);
|
||||
alert(`❌ Validation failed: invalid or non existent commited_in for state ${stateId}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// We must check that this transaction indeed spend the commited_in we have in the certificate
|
||||
let found = false;
|
||||
for (const vin of txInfo.vin) {
|
||||
if (vin.txid === prevTxid) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
console.error(`Validation error: new state doesn't spend previous state commitment transaction`);
|
||||
alert('❌ Validation failed: Unconsistent commitment transactions history.');
|
||||
return;
|
||||
}
|
||||
|
||||
// set found back to false for next check
|
||||
found = false;
|
||||
|
||||
// is the state_id commited in the transaction?
|
||||
for (const vout of txInfo.vout) {
|
||||
console.log(vout);
|
||||
if (vout.scriptpubkey_type && vout.scriptpubkey_type === 'op_return') {
|
||||
found = true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vout.scriptpubkey_asm) {
|
||||
const hash = extractHexFromScriptAsm(vout.scriptpubkey_asm);
|
||||
if (hash) {
|
||||
if (hash !== stateId) {
|
||||
console.error(`Validation error: expected stateId ${stateId}, got ${hash}`);
|
||||
alert('❌ Validation failed: Transaction does not commit to that state.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
alert('❌ Validation failed: Transaction does not contain data.');
|
||||
return;
|
||||
}
|
||||
|
||||
// set found back to false for next check
|
||||
found = false;
|
||||
|
||||
for (const label of Object.keys(state.certificate.pcd_commitment)) {
|
||||
// Compute the hash for this label
|
||||
console.log(`Computing hash with label ${label}`)
|
||||
const fileHex = service.getHashForFile(commitedIn, label, fileBlob);
|
||||
console.log(`Found hash ${fileHex}`);
|
||||
found = state.commitmentHashes.includes(fileHex);
|
||||
if (found) break;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
alert('✅ Validation successful: file hash found in pcd_commitment.');
|
||||
} else {
|
||||
alert('❌ Validation failed: file hash NOT found in pcd_commitment.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTransaction(txid: string): Promise<TransactionInfo> {
|
||||
const url = `https://mempool.4nkweb.com/api/tx/${txid}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch outspend status: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const outspend: TransactionInfo = await response.json();
|
||||
return outspend;
|
||||
}
|
||||
|
||||
function extractHexFromScriptAsm(scriptAsm: string): string | null {
|
||||
const parts = scriptAsm.trim().split(/\s+/);
|
||||
const last = parts[parts.length - 1];
|
||||
|
||||
// Basic validation: must be 64-char hex (32 bytes)
|
||||
if (/^[0-9a-fA-F]{64}$/.test(last)) {
|
||||
return last.toLowerCase();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
196
src/pages/account/key-value-section.ts
Normal file
@ -0,0 +1,196 @@
|
||||
import { ValidationRule, RoleDefinition } from '../../../pkg/sdk_client.js';
|
||||
import { showValidationRuleModal } from '../../components/validation-rule-modal/validation-rule-modal.js';
|
||||
|
||||
export function createKeyValueSection(title: string, id: string, isRoleSection = false) {
|
||||
const section = document.createElement('div');
|
||||
section.id = id;
|
||||
section.style.cssText = 'margin-bottom: 2rem; background: #fff; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1);';
|
||||
|
||||
const titleEl = document.createElement('h2');
|
||||
titleEl.textContent = title;
|
||||
titleEl.style.cssText = 'font-size: 1.25rem; font-weight: bold; margin-bottom: 1rem;';
|
||||
section.appendChild(titleEl);
|
||||
|
||||
const rowContainer = document.createElement('div');
|
||||
section.appendChild(rowContainer);
|
||||
|
||||
const addBtn = document.createElement('button');
|
||||
addBtn.textContent = '+ Add Row';
|
||||
addBtn.style.cssText = `
|
||||
margin-top: 1rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid #888;
|
||||
border-radius: 0.375rem;
|
||||
background-color: #f9f9f9;
|
||||
cursor: pointer;
|
||||
`;
|
||||
section.appendChild(addBtn);
|
||||
|
||||
const roleRowStates: {
|
||||
roleNameInput: HTMLInputElement;
|
||||
membersInput: HTMLInputElement;
|
||||
storagesInput: HTMLInputElement;
|
||||
validationRules: ValidationRule[];
|
||||
}[] = [];
|
||||
type fileBlob = {
|
||||
type: string,
|
||||
data: Uint8Array
|
||||
};
|
||||
const nonRoleRowStates: {
|
||||
keyInput: HTMLInputElement,
|
||||
valueInput: HTMLInputElement,
|
||||
fileInput: HTMLInputElement,
|
||||
fileBlob: fileBlob | null
|
||||
}[] = [];
|
||||
|
||||
const inputStyle = 'flex: 1; height: 2.5rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 0.375rem;';
|
||||
|
||||
const createRow = () => {
|
||||
const row = document.createElement('div');
|
||||
row.style.cssText = 'display: flex; gap: 1rem; margin-bottom: 0.5rem; align-items: center;';
|
||||
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.textContent = '🗑️';
|
||||
deleteBtn.style.cssText = 'background: none; border: none; font-size: 1.2rem; cursor: pointer;';
|
||||
deleteBtn.onclick = () => {
|
||||
row.remove();
|
||||
updateDeleteButtons();
|
||||
};
|
||||
|
||||
if (isRoleSection) {
|
||||
const roleName = document.createElement('input');
|
||||
const members = document.createElement('input');
|
||||
const storages = document.createElement('input');
|
||||
|
||||
roleName.placeholder = 'Role name';
|
||||
members.placeholder = 'members';
|
||||
storages.placeholder = 'storages';
|
||||
[roleName, members, storages].forEach(input => {
|
||||
input.type = 'text';
|
||||
input.style.cssText = inputStyle;
|
||||
});
|
||||
|
||||
const ruleButton = document.createElement('button');
|
||||
ruleButton.textContent = 'Add Validation Rule';
|
||||
ruleButton.style.cssText = 'padding: 0.3rem 0.75rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;';
|
||||
|
||||
const rules: ValidationRule[] = [];
|
||||
ruleButton.onclick = () => {
|
||||
showValidationRuleModal(rule => {
|
||||
rules.push(rule);
|
||||
ruleButton.textContent = `Rules (${rules.length})`;
|
||||
});
|
||||
};
|
||||
|
||||
row.appendChild(roleName);
|
||||
row.appendChild(members);
|
||||
row.appendChild(storages);
|
||||
row.appendChild(ruleButton);
|
||||
row.appendChild(deleteBtn);
|
||||
|
||||
roleRowStates.push({ roleNameInput: roleName, membersInput: members, storagesInput: storages, validationRules: rules });
|
||||
} else {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.style.display = 'none';
|
||||
fileInput.onchange = async () => {
|
||||
const file = fileInput.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const buffer = await file.arrayBuffer();
|
||||
const uint8 = new Uint8Array(buffer);
|
||||
|
||||
rowState.fileBlob = {
|
||||
type: file.type,
|
||||
data: uint8,
|
||||
};
|
||||
|
||||
valueInput.value = `📄 ${file.name}`;
|
||||
valueInput.disabled = true;
|
||||
attachBtn.textContent = `📎 ${file.name}`;
|
||||
};
|
||||
|
||||
const attachBtn = document.createElement('button');
|
||||
attachBtn.textContent = '📎 Attach';
|
||||
attachBtn.style.cssText = 'padding: 0.3rem 0.75rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;';
|
||||
attachBtn.onclick = () => fileInput.click();
|
||||
|
||||
const keyInput = document.createElement('input');
|
||||
const valueInput = document.createElement('input');
|
||||
|
||||
const rowState = {
|
||||
keyInput,
|
||||
valueInput,
|
||||
fileInput,
|
||||
fileBlob: null as fileBlob | null
|
||||
};
|
||||
nonRoleRowStates.push(rowState);
|
||||
|
||||
keyInput.placeholder = 'Key';
|
||||
valueInput.placeholder = 'Value';
|
||||
[keyInput, valueInput].forEach(input => {
|
||||
input.type = 'text';
|
||||
input.style.cssText = inputStyle;
|
||||
});
|
||||
|
||||
row.appendChild(keyInput);
|
||||
row.appendChild(valueInput);
|
||||
|
||||
row.appendChild(attachBtn);
|
||||
row.appendChild(fileInput);
|
||||
|
||||
row.appendChild(deleteBtn);
|
||||
}
|
||||
|
||||
rowContainer.appendChild(row);
|
||||
updateDeleteButtons();
|
||||
};
|
||||
|
||||
const updateDeleteButtons = () => {
|
||||
const rows = Array.from(rowContainer.children);
|
||||
rows.forEach(row => {
|
||||
const btn = row.querySelector('button:last-child') as HTMLButtonElement;
|
||||
if (rows.length === 1) {
|
||||
btn.disabled = true;
|
||||
btn.style.visibility = 'hidden';
|
||||
} else {
|
||||
btn.disabled = false;
|
||||
btn.style.visibility = 'visible';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
createRow();
|
||||
addBtn.addEventListener('click', createRow);
|
||||
|
||||
return {
|
||||
element: section,
|
||||
getData: () => {
|
||||
if (isRoleSection) {
|
||||
const data: Record<string, RoleDefinition> = {};
|
||||
for (const row of roleRowStates) {
|
||||
const key = row.roleNameInput.value.trim();
|
||||
if (!key) continue;
|
||||
data[key] = {
|
||||
members: row.membersInput.value.split(',').map(x => x.trim()).filter(Boolean),
|
||||
storages: row.storagesInput.value.split(',').map(x => x.trim()).filter(Boolean),
|
||||
validation_rules: row.validationRules
|
||||
};
|
||||
}
|
||||
return data;
|
||||
} else {
|
||||
const data: Record<string, string | fileBlob> = {};
|
||||
for (const row of nonRoleRowStates) {
|
||||
const key = row.keyInput.value.trim();
|
||||
if (!key) continue;
|
||||
if (row.fileBlob) {
|
||||
data[key] = row.fileBlob;
|
||||
} else {
|
||||
data[key] = row.valueInput.value.trim();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
95
src/pages/account/process-creation.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { createKeyValueSection } from './key-value-section';
|
||||
import { loadValidationRuleModal } from '../../components/validation-rule-modal/validation-rule-modal';
|
||||
import Services from '../../services/service';
|
||||
import { RoleDefinition } from '../../../pkg/sdk_client.js';
|
||||
|
||||
export async function getProcessCreation(container: HTMLElement) {
|
||||
await loadValidationRuleModal();
|
||||
|
||||
container.style.display = 'block';
|
||||
container.innerHTML = `<div class="parameter-header">Process Creation</div>`;
|
||||
const privateSec = createKeyValueSection('Private Data', 'private-section');
|
||||
const publicSec = createKeyValueSection('Public Data', 'public-section');
|
||||
const rolesSec = createKeyValueSection('Roles', 'roles-section', true);
|
||||
|
||||
container.appendChild(privateSec.element);
|
||||
container.appendChild(publicSec.element);
|
||||
container.appendChild(rolesSec.element);
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = 'Create Process';
|
||||
btn.style.cssText = `
|
||||
display: block;
|
||||
margin: 2rem auto 0;
|
||||
padding: 0.75rem 2rem;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
background-color: #4f46e5;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
btn.onclick = async () => {
|
||||
const privateData = privateSec.getData();
|
||||
const publicData = publicSec.getData();
|
||||
const roles = rolesSec.getData() as Record<string, RoleDefinition>;
|
||||
|
||||
console.log('Private:', privateData);
|
||||
console.log('Public:', publicData);
|
||||
console.log('Roles:', roles);
|
||||
|
||||
const service = await Services.getInstance();
|
||||
|
||||
const createProcessResult = await service.createProcess(privateData, publicData, roles);
|
||||
const processId = createProcessResult.updated_process!.process_id;
|
||||
const stateId = createProcessResult.updated_process!.current_process.states[0].state_id;
|
||||
await service.handleApiReturn(createProcessResult);
|
||||
|
||||
// Now we want to validate the update and register the first state of our new process
|
||||
const updateProcessResult = await service.createPrdUpdate(processId, stateId);
|
||||
await service.handleApiReturn(createProcessResult);
|
||||
|
||||
const approveChangeResult = await service.approveChange(processId, stateId);
|
||||
await service.handleApiReturn(approveChangeResult);
|
||||
if (approveChangeResult) {
|
||||
const process = await service.getProcess(processId);
|
||||
if (!process) return;
|
||||
const newState = service.getStateFromId(process, stateId);
|
||||
if (!newState) return;
|
||||
for (const label of Object.keys(newState.keys)) {
|
||||
const hash = newState.pcd_commitment[label];
|
||||
const encryptedData = await service.getBlobFromDb(hash);
|
||||
if (!encryptedData) continue;
|
||||
const filename = `${label}-${hash.slice(0,8)}.bin`;
|
||||
|
||||
const blob = new Blob([encryptedData], { type: "application/octet-stream" });
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
link.click();
|
||||
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 1000);
|
||||
}
|
||||
|
||||
if (typeof (service as any).generateProcessPdf === 'function') {
|
||||
await (service as any).generateProcessPdf(processId, newState);
|
||||
}
|
||||
|
||||
// Add processId to the state we export
|
||||
(newState as any)['process_id'] = processId;
|
||||
const blob = new Blob([JSON.stringify(newState as unknown as object, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `process_${processId}_${stateId}.json`;
|
||||
a.click();
|
||||
|
||||
URL.revokeObjectURL(url); // Clean up
|
||||
}
|
||||
};
|
||||
|
||||
container.appendChild(btn);
|
||||
}
|
||||
66
src/pages/account/process.ts
Normal file
@ -0,0 +1,66 @@
|
||||
export function createProcessTab(container: HTMLElement, processes: { name: string, publicData: Record<string, any> }[]): HTMLElement {
|
||||
container.id = 'process-tab';
|
||||
container.style.display = 'block';
|
||||
container.style.cssText = 'padding: 1.5rem;';
|
||||
|
||||
const title = document.createElement('h2');
|
||||
title.textContent = 'Processes';
|
||||
title.style.cssText = 'font-size: 1.5rem; font-weight: bold; margin-bottom: 1rem;';
|
||||
container.appendChild(title);
|
||||
|
||||
processes.forEach(proc => {
|
||||
const card = document.createElement('div');
|
||||
card.style.cssText = 'margin-bottom: 1rem; padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; background: #fff;';
|
||||
|
||||
const nameEl = document.createElement('h3');
|
||||
nameEl.textContent = proc.name;
|
||||
nameEl.style.cssText = 'font-size: 1.2rem; font-weight: bold; margin-bottom: 0.5rem;';
|
||||
card.appendChild(nameEl);
|
||||
|
||||
const dataList = document.createElement('div');
|
||||
for (const [key, value] of Object.entries(proc.publicData)) {
|
||||
const item = document.createElement('div');
|
||||
item.style.cssText = 'margin-bottom: 0.5rem;';
|
||||
|
||||
const label = document.createElement('strong');
|
||||
label.textContent = key + ': ';
|
||||
item.appendChild(label);
|
||||
|
||||
// Let's trim the quotes
|
||||
const trimmed = value.replace(/^'|'$/g, '');
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(trimmed);
|
||||
} catch (_) {
|
||||
parsed = trimmed;
|
||||
}
|
||||
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
const saveBtn = document.createElement('button');
|
||||
saveBtn.textContent = '💾 Save as JSON';
|
||||
saveBtn.style.cssText = 'margin-left: 0.5rem; padding: 0.25rem 0.5rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;';
|
||||
saveBtn.onclick = () => {
|
||||
const blob = new Blob([JSON.stringify(parsed, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${proc.name}_${key}.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
item.appendChild(saveBtn);
|
||||
} else {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = String(parsed);
|
||||
item.appendChild(span);
|
||||
}
|
||||
|
||||
dataList.appendChild(item);
|
||||
}
|
||||
|
||||
card.appendChild(dataList);
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||