From c3b64e4bb99626b4fbf4e9ca39c819b6f308bd89 Mon Sep 17 00:00:00 2001 From: Nebulae Date: Fri, 20 Feb 2026 15:36:48 +0100 Subject: [PATCH] feat(Synchro pages test) --- .ai-agent.md | 16 -- .ai-rules.md | 10 +- .claude/settings.local.json | 7 +- CLAUDE.md | 7 +- PROJECT_STRUCTURE.md | 256 ++++-------------- app/Filament/Pages/Synchronisations.php | 171 ++++++++++++ app/Jobs/RunSyncCommand.php | 64 +++++ lang/fr/members.php | 2 +- .../pages/partials/sync-heading.blade.php | 10 + .../pages/partials/sync-status.blade.php | 12 + .../filament/pages/synchronisations.blade.php | 109 ++++++++ 11 files changed, 437 insertions(+), 227 deletions(-) delete mode 100644 .ai-agent.md create mode 100644 app/Filament/Pages/Synchronisations.php create mode 100644 app/Jobs/RunSyncCommand.php create mode 100644 resources/views/filament/pages/partials/sync-heading.blade.php create mode 100644 resources/views/filament/pages/partials/sync-status.blade.php create mode 100644 resources/views/filament/pages/synchronisations.blade.php diff --git a/.ai-agent.md b/.ai-agent.md deleted file mode 100644 index 19da209..0000000 --- a/.ai-agent.md +++ /dev/null @@ -1,16 +0,0 @@ -Tu es un agent IA senior specialise en Laravel, Filament, React et Tailwind. - -Tu respectes STRICTEMENT les regles du fichier .ai-rules.md et tu parcours bien le ficher .PROJECT_STRUCTURE.md pour avoir le contexte du projet. - -Ton role : -- proposer des solutions simples et maintenables -- respecter l'architecture existante -- generer du code uniquement apres validation explicite -- inclure les tests pour chaque feature -- ne jamais refactorer sans demande explicite - -Tu dois toujours : -1. Analyser la demande -2. Proposer un plan d'action -3. Attendre validation -4. Generer le code diff --git a/.ai-rules.md b/.ai-rules.md index 4368079..06de1ba 100644 --- a/.ai-rules.md +++ b/.ai-rules.md @@ -1,10 +1,9 @@ # Regles de developpement du projet ## Stack -- Backend : Laravel + Filament v4 +- Backend : Laravel 12 + Filament v4 - Frontend : React + Tailwind -- Tests : Pest ou PHPUnit (preciser) -- IDE : PhpStorm +- Tests : PHPUnit 11 ## Architecture Backend - Utiliser des Services @@ -24,7 +23,8 @@ - Commentaires uniquement si necessaire et explicites - Code simple > code "clever" -## Workflow IA -- Ne jamais modifier le code sans validation +## Comportement attendu +- Ne jamais modifier le code sans validation explicite +- Ne jamais refactorer sans demande explicite - Toujours proposer un plan avant d'ecrire du code - Toujours expliquer brievement ce qui va etre ajoute/modifie diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a9989ad..c7529f9 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,12 @@ "Bash(vendor/bin/pint:*)", "Bash(php artisan test:*)", "Bash(php artisan migrate:*)", - "Bash(php artisan make:filament-relation-manager:*)" + "Bash(php artisan make:filament-relation-manager:*)", + "mcp__laravel-boost__list-artisan-commands", + "mcp__laravel-boost__tinker", + "Bash(php artisan make:job:*)", + "mcp__laravel-boost__last-error", + "Bash(php artisan view:clear:*)" ] }, "enableAllProjectMcpServers": true, diff --git a/CLAUDE.md b/CLAUDE.md index d766838..41be2fb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,16 +1,15 @@ # Configuration Claude Code - Projet Roxane -Lire et appliquer les fichiers suivants avant toute action : +Lire et appliquer avant toute action : -- [.ai-rules.md](.ai-rules.md) : regles de developpement, stack, architecture, style de code -- [.ai-agent.md](.ai-agent.md) : comportement attendu de l'agent IA +- [.ai-rules.md](.ai-rules.md) : regles de developpement, stack, architecture, style de code, comportement attendu Lire avant chaque session de code et mettre à jour après ajout ou modification de fonctionnalités : - [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md) ## Rappels critiques -- Backend : Laravel + Filament v4 | Frontend : React + Tailwind +- Backend : Laravel 12 + Filament v4 | Frontend : React + Tailwind - Architecture : Services (pas de Repository pattern), Controllers fins - Toujours proposer un plan avant de coder - Ne jamais modifier de code sans validation explicite diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 730e067..988ff24 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -19,16 +19,12 @@ Elle gère les membres, les cotisations, et s'intègre avec des services tiers ( ## Modeles Eloquent ### User - -- **Table** : `users` -- **Traits** : HasRoles, TwoFactorAuthenticatable, HasFactory, Notifiable +- **Traits** : HasRoles, TwoFactorAuthenticatable - **Relations** : `members()` hasMany(Member) -- **Role** : Compte utilisateur de l'application. Implemente `FilamentUser` pour l'acces admin. +- **Role** : Implemente `FilamentUser` pour l'acces admin. ### Member - - **Table** : `members` (soft deletes) -- **Champs cles** : `user_id`, `dolibarr_id`, `keycloak_id`, `status`, `nature`, `group_id`, lastname, firstname, email, company, date_of_birth, address, zipcode, city, country, phone1, phone2, public_membership, website_url - **Statuts** : draft, valid, pending, cancelled, excluded - **Natures** : physical, legal - **Relations** : @@ -41,9 +37,7 @@ Elle gère les membres, les cotisations, et s'intègre avec des services tiers ( - **Methodes** : `lastMembership()`, `hasService()`, `isExpired()` ### Membership - - **Table** : `memberships` (soft deletes) -- **Champs cles** : `member_id`, `admin_id`, `package_id`, start_date, end_date, status, validation_date, payment_method, amount, payment_status, note_public, note_private, dolibarr_id - **Statuts** : active, expired, pending - **Statuts paiement** : paid, unpaid, partial - **Relations** : @@ -53,176 +47,82 @@ Elle gère les membres, les cotisations, et s'intègre avec des services tiers ( - `services()` belongsToMany(Service) via `services_memberships` ### Package - - **Table** : `packages` -- **Champs** : identifier, name, description, price, is_active - **Donnees** : custom (1EUR), one-year (12EUR), two-years (24EUR) ### Service - - **Table** : `services` -- **Champs** : identifier, name, description, url, icon - **Relations** : `memberships()` belongsToMany(Membership) -- **Donnees** : mail (RoundCube), file2link, nextcloud, sympa (listes de diffusion), webhosting +- **Donnees** : mail (RoundCube), file2link, nextcloud, sympa, webhosting ### MemberGroup - - **Table** : `member_groups` -- **Champs** : name, description, identifier - **Relations** : `members()` hasMany(Member) - **Donnees** : admin-interface, website ### Contact - - **Table** : `contacts` -- **Champs** : firstname, lastname, email, address, subject, message ### IspconfigMember - - **Table** : `ispconfigs_members` -- **Champs** : member_id, ispconfig_client_id, ispconfig_service_user_id, email, type, data - **Cast** : `type` vers IspconfigType enum, `data` vers array ### NextCloudMember - - **Table** : `nextclouds_members` -- **Champs** : member_id, nextcloud_user_id, data --- ## Enums -| Enum | Valeurs | -| -------------- | ------------------------------------- | -| IspconfigType | MAIL ("Email"), WEB ("Hebergement"), OTHER ("Autre") | +| Enum | Valeurs | +| ------------- | ----------------------------------------------------- | +| IspconfigType | MAIL ("Email"), WEB ("Hebergement"), OTHER ("Autre") | --- ## Services (couche metier) -### ContactService - -- `registerNewContactRequest(array $data)` : cree un enregistrement Contact - -### MemberService - -- `registerNewMember(array $data)` : cree un Member + Membership pending, assigne au groupe "website", declenche MemberRegistered -- `deactivateMember(Member $member)` : passe le statut a "excluded", desactive la cotisation, detache les services - -### DolibarrService - -Integration avec l'ERP Dolibarr via API REST. - -- `getAllMembers()` : recupere la liste des membres -- `getMemberSubscriptions()` : recupere les cotisations d'un membre -- `updateMember()` : met a jour un membre - -### ISPConfigService (base) - -Integration via SOAP. Sous-classes : - -**ISPConfigMailService** (serveur mail) : -- `getAllMailDomains()`, `getAllMailUsers()` -- `getMailDomainsForClient()`, `getMailUsersForDomain()` -- `getMailUserDetails()` : quota, usage, protocoles -- `updateMailUser()` : modification d'un compte mail - -**ISPConfigWebService** (serveur web) : -- `getAllWebsites()`, `getAllDatabases()`, `getAllFtpUsers()`, `getAllShellUsers()`, `getAllDnsZones()` -- `getWebsiteCompleteInfo()` : infos agregees d'un site -- Gestion de cache avec invalidation - -### NextcloudService - -Integration avec l'API OCS Nextcloud. - -- `disableUserByEmail()`, `disableUserById()` -- `listUsers()`, `getUserDetails()` -- `findUserIdByEmail()` : recherche avec cache 7 jours +| Service | Responsabilite | +| ---------------------- | ----------------------------------------------------------- | +| ContactService | Creation de demandes de contact | +| MemberService | Inscription et desactivation de membres | +| DolibarrService | Integration ERP via API REST | +| ISPConfigMailService | Gestion comptes mail via SOAP | +| ISPConfigWebService | Gestion hebergement web via SOAP (avec cache) | +| NextcloudService | Gestion comptes Nextcloud via OCS (avec cache 7 jours) | --- ## Commandes Artisan (synchronisation) -| Commande | Description | -| --------------------------------- | -------------------------------------------------------- | -| `sync:dolibarr-members` | Importe les membres et cotisations depuis Dolibarr | -| `members:cleanup-expired` | Desactive les membres expires (Dolibarr + ISPConfig mail + Nextcloud). Supporte `--dry-run` | -| `sync:ispconfig-mail-members` | Lie les membres a leurs comptes mail ISPConfig (@retzien.fr) | -| `sync:ispconfig-web-members` | Lie les membres a leurs comptes d'hebergement web | -| `sync:nextcloud-members` | Lie les membres a leurs comptes Nextcloud | -| `sync:services-members` | Synchronise les services associes aux membres | - ---- - -## Routes - -### Publiques - -| Methode | URI | Description | -| --------- | ---------------- | ------------------------------- | -| GET | `/welcome` | Page d'accueil (home) | -| GET | `/` | Page de maintenance | -| GET/POST | `/contact` | Formulaire de contact | -| GET/POST | `/membership` | Formulaire d'adhesion | - -### Authentification (guest) - -| Methode | URI | Description | -| --------- | ------------------------ | -------------------------- | -| GET/POST | `/login` | Connexion | -| GET/POST | `/register` | Inscription | -| GET/POST | `/forgot-password` | Mot de passe oublie | -| GET/POST | `/reset-password/{token}`| Reinitialisation | -| GET/POST | `/two-factor-challenge` | Challenge 2FA | - -### Authentifie - -| Methode | URI | Description | -| ------------- | ------------------------ | -------------------------- | -| GET | `/dashboard` | Tableau de bord | -| GET/PATCH/DEL | `/settings/profile` | Profil utilisateur | -| GET/PUT | `/settings/password` | Mot de passe | -| GET | `/settings/appearance` | Theme / apparence | -| GET | `/settings/two-factor` | Configuration 2FA | -| GET | `/verify-email` | Verification email | -| POST | `/logout` | Deconnexion | - -### Admin (Filament) - -| URI | Ressource | -| -------------------------- | ---------------------- | -| `/admin` | Dashboard | -| `/admin/members` | Gestion des membres | -| `/admin/memberships` | Gestion des cotisations| -| `/admin/packages` | Formules d'adhesion | -| `/admin/services` | Services numeriques | -| `/admin/member-groups` | Groupes de membres | -| `/admin/users` | Utilisateurs | -| `/admin/shield/roles` | Roles et permissions | +| Commande | Description | +| ------------------------------- | ------------------------------------------------------------------------------ | +| `sync:dolibarr-members` | Importe les membres et cotisations depuis Dolibarr | +| `members:cleanup-expired` | Desactive les membres expires (Dolibarr + ISPConfig + Nextcloud). `--dry-run` | +| `sync:ispconfig-mail-members` | Lie les membres a leurs comptes mail ISPConfig (@retzien.fr) | +| `sync:ispconfig-web-members` | Lie les membres a leurs comptes d'hebergement web | +| `sync:nextcloud-members` | Lie les membres a leurs comptes Nextcloud | +| `sync:services-members` | Synchronise les services associes aux membres | --- ## Panel Admin (Filament v4) -### Ressources - -Chaque ressource suit une structure modulaire : +### Structure des ressources ``` app/Filament/Resources// - ├── Resource.php # Definition (modele, navigation, schema, table) - ├── Schemas/Form.php # Formulaire - ├── Tables/sTable.php # Tableau - ├── Pages/ - │ ├── Lists.php - │ ├── Create.php - │ └── Edit.php - ├── Widgets/ # (optionnel) - └── RelationManagers/ # (optionnel) + ├── Resource.php + ├── Schemas/Form.php + ├── Tables/sTable.php + ├── Pages/ (List, Create, Edit) + ├── Widgets/ (optionnel) + └── RelationManagers/ (optionnel) ``` -**Ressources disponibles** : MemberResource, MembershipResource, PackageResource, ServiceResource, MemberGroupResource, UserResource +**Ressources** : MemberResource, MembershipResource, PackageResource, ServiceResource, MemberGroupResource, UserResource + +**Pages custom** : Synchronisations (groupe "Paramètres" — lancement manuel des commandes Artisan) **Widgets** : MemberCount, MembershipsChart @@ -236,8 +136,6 @@ Roles : - `super_admin` : acces complet - `panel_user` : acces au panel avec permissions specifiques -Chaque modele a sa Policy associee verifiant les permissions Spatie. - --- ## Frontend (React + Inertia v2) @@ -246,92 +144,50 @@ Chaque modele a sa Policy associee verifiant les permissions Spatie. ``` resources/js/pages/ - ├── welcome.tsx # Accueil - ├── maintenance.tsx # Maintenance - ├── dashboard.tsx # Tableau de bord (placeholder) - ├── auth/ - │ ├── login.tsx - │ ├── register.tsx - │ ├── forgot-password.tsx - │ ├── reset-password.tsx - │ ├── verify-email.tsx - │ ├── confirm-password.tsx - │ └── two-factor-challenge.tsx - ├── forms/ - │ ├── contact.tsx # Formulaire de contact - │ └── membership.tsx # Formulaire d'adhesion - └── settings/ - ├── profile.tsx - ├── password.tsx - ├── two-factor.tsx - └── appearance.tsx + ├── welcome.tsx / maintenance.tsx / dashboard.tsx + ├── auth/ (login, register, forgot-password, reset-password, verify-email, two-factor-challenge) + ├── forms/ (contact.tsx, membership.tsx) + └── settings/ (profile, password, two-factor, appearance) ``` ### Composants principaux ``` resources/js/components/ - ├── app-shell.tsx # Layout principal - ├── app-header.tsx / app-sidebar.tsx + ├── app-shell.tsx / app-header.tsx / app-sidebar.tsx ├── nav-main.tsx / nav-user.tsx / nav-footer.tsx - ├── breadcrumbs.tsx - ├── flash-message.tsx - ├── two-factor-setup-modal.tsx - ├── appearance-tabs.tsx - └── ui/ # Composants Shadcn/ui + ├── breadcrumbs.tsx / flash-message.tsx + ├── two-factor-setup-modal.tsx / appearance-tabs.tsx + └── ui/ (Shadcn/ui) ``` --- ## Notifications -| Classe | Canal | Description | -| ---------------------------- | ----- | ---------------------------------- | -| SubscriptionExpiredPhase1 | Email | Notification d'expiration d'adhesion (queued) | - ---- - -## Configuration externe - -Definie dans `config/services.php` via variables d'environnement : - -| Service | Type d'integration | Description | -|--------|---------------------|--------------------------------------------| -| Dolibarr | API REST + htaccess | ERP - gestion des adherents et cotisations | -| ISPConfig | SOAP (3 serveurs) | Mail, hebergement web, test. Cache 5 min | -| Nextcloud | API OCS | Comptes cloud des membres | -| Sympa | Mail Listing | Liste de diffusion mail (à venir) | +| Classe | Canal | Description | +| ------------------------- | ----- | --------------------------------------------- | +| SubscriptionExpiredPhase1 | Email | Notification d'expiration d'adhesion (queued) | --- ## Localisation -Langues supportees : **francais** (fr), **anglais** (en) - -Fichiers de traduction par modele dans `lang/{locale}/` : contacts, members, memberships, packages, services, users, member_groups. - ---- - -## Seeders - -Le `DatabaseSeeder` cree : -- 1 super admin (contact@nebulae-design.com) -- 2 groupes de membres (admin-interface, website) -- 3 formules (custom, one-year, two-years) -- 5 services (mail, file2link, nextcloud, sympa, webhosting) -- 1 utilisateur test (JaneDoe) avec profil membre et cotisation active +Langues : **fr**, **en** — fichiers dans `lang/{locale}/` : contacts, members, memberships, packages, services, users, member_groups. --- ## TODOs identifies dans le code -| Fichier | TODO | -| -------------------------------- | --------------------------------------------------- | -| ContactService | Envoyer un email a l'administrateur | -| MemberService | Envoyer des emails au membre + admin a la desactivation | -| SubscriptionExpiredPhase1 | Creer un template generique + UI backend pour le contenu | -| User.php | Restreindre l'acces admin en prod aux emails @retzien.fr | -| SyncDolibarrMembers | Exporter la methode toDate() dans un service/helper | -| SyncISPConfigMailMembers | Gerer plusieurs emails par membre | -| SyncISPConfigMailMembers | Ajouter le suivi ispconfig_client_id | -| MembershipFormController | Supprimer le `dd()` de debug (ligne 37) | +| Fichier | TODO | +|---------------------------|----------------------------------------------------------------------------| +| ContactService | Envoyer un email a l'administrateur | +| MemberService | Envoyer des emails au membre + admin a la desactivation | +| SubscriptionExpiredPhase1 | Creer un template generique + UI backend pour le contenu | +| User.php | Restreindre l'acces admin en prod aux emails @retzien.fr | +| SyncDolibarrMembers | Exporter la methode toDate() dans un service/helper | +| SyncISPConfigMailMembers | Gerer plusieurs emails par membre | +| SyncISPConfigMailMembers | Ajouter le suivi ispconfig_client_id | +| Global | Refactoriser pour rendre générique le projet Roxane (ERP pour association) | +| Traduction | Crawler le prrojet pour retrouver toutes les clés manquantes | +| Global | PHPstan niveau 8 | diff --git a/app/Filament/Pages/Synchronisations.php b/app/Filament/Pages/Synchronisations.php new file mode 100644 index 0000000..069e9fd --- /dev/null +++ b/app/Filament/Pages/Synchronisations.php @@ -0,0 +1,171 @@ +user()?->hasRole('super_admin') ?? false; + } + + public function getCommandStatus(string $key): array + { + return Cache::get("sync_run.{$key}", [ + 'status' => 'idle', + 'output' => null, + 'started_at' => null, + 'finished_at' => null, + ]); + } + + public function hasRunningCommands(): bool + { + foreach (self::CACHE_KEYS as $key) { + if (in_array($this->getCommandStatus($key)['status'], ['pending', 'running'])) { + return true; + } + } + + return false; + } + + private function enqueueCommand(string $key, string $command, array $parameters = []): void + { + Cache::put("sync_run.{$key}", [ + 'status' => 'pending', + 'output' => null, + 'started_at' => null, + 'finished_at' => null, + ], now()->addHour()); + + RunSyncCommand::dispatch($command, $parameters, $key); + } + + public function syncDolibarrAction(): Action + { + return Action::make('syncDolibarr') + ->label('Lancer') + ->requiresConfirmation() + ->modalHeading('Synchronisation Dolibarr') + ->modalDescription('Importer les membres et cotisations depuis Dolibarr.') + ->modalSubmitActionLabel('Lancer') + ->disabled(fn () => in_array($this->getCommandStatus('dolibarr')['status'], ['pending', 'running'])) + ->action(fn () => $this->enqueueCommand('dolibarr', 'sync:dolibarr-members')); + } + + public function cleanupExpiredAction(): Action + { + return Action::make('cleanupExpired') + ->label('Lancer') + ->modalHeading('Désactiver les membres expirés') + ->modalDescription('Désactive les membres expirés dans Dolibarr, ISPConfig et Nextcloud.') + ->modalSubmitActionLabel('Lancer') + ->schema([ + Toggle::make('dry_run') + ->label('Mode simulation (dry-run)') + ->helperText('Simule l\'opération sans effectuer de modifications.') + ->default(true), + ]) + ->disabled(fn () => in_array($this->getCommandStatus('cleanup_expired')['status'], ['pending', 'running'])) + ->action(function (array $data) { + $parameters = $data['dry_run'] ? ['--dry-run' => true] : []; + $this->enqueueCommand('cleanup_expired', 'members:cleanup-expired', $parameters); + }); + } + + public function syncISPConfigMailAction(): Action + { + return Action::make('syncISPConfigMail') + ->label('Lancer') + ->requiresConfirmation() + ->modalHeading('Synchronisation ISPConfig Mail') + ->modalDescription('Lie les membres à leurs comptes mail ISPConfig (@retzien.fr).') + ->modalSubmitActionLabel('Lancer') + ->disabled(fn () => in_array($this->getCommandStatus('ispconfig_mail')['status'], ['pending', 'running'])) + ->action(fn () => $this->enqueueCommand('ispconfig_mail', 'sync:ispconfig-mail-members')); + } + + public function syncISPConfigWebAction(): Action + { + return Action::make('syncISPConfigWeb') + ->label('Lancer') + ->modalHeading('Synchronisation ISPConfig Web') + ->modalDescription('Lie les membres à leurs comptes d\'hébergement web.') + ->modalSubmitActionLabel('Lancer') + ->schema([ + Toggle::make('refresh_cache') + ->label('Vider le cache ISPConfig') + ->helperText('Vide le cache avant la synchronisation.') + ->default(false), + ]) + ->disabled(fn () => in_array($this->getCommandStatus('ispconfig_web')['status'], ['pending', 'running'])) + ->action(function (array $data) { + $parameters = $data['refresh_cache'] ? ['--refresh-cache' => true] : []; + $this->enqueueCommand('ispconfig_web', 'sync:ispconfig-web-members', $parameters); + }); + } + + public function syncNextcloudAction(): Action + { + return Action::make('syncNextcloud') + ->label('Lancer') + ->modalHeading('Synchronisation Nextcloud') + ->modalDescription('Lie les membres à leurs comptes Nextcloud.') + ->modalSubmitActionLabel('Lancer') + ->schema([ + Toggle::make('dry_run') + ->label('Mode simulation (dry-run)') + ->helperText('Simule l\'opération sans effectuer de modifications.') + ->default(false), + ]) + ->disabled(fn () => in_array($this->getCommandStatus('nextcloud')['status'], ['pending', 'running'])) + ->action(function (array $data) { + $parameters = $data['dry_run'] ? ['--dry-run' => true] : []; + $this->enqueueCommand('nextcloud', 'nextcloud:sync-members', $parameters); + }); + } + + public function syncServicesAction(): Action + { + return Action::make('syncServices') + ->label('Lancer') + ->requiresConfirmation() + ->modalHeading('Synchronisation des services') + ->modalDescription('Synchronise les services associés aux membres actifs.') + ->modalSubmitActionLabel('Lancer') + ->disabled(fn () => in_array($this->getCommandStatus('services')['status'], ['pending', 'running'])) + ->action(fn () => $this->enqueueCommand('services', 'memberships:sync-services')); + } +} diff --git a/app/Jobs/RunSyncCommand.php b/app/Jobs/RunSyncCommand.php new file mode 100644 index 0000000..d70771a --- /dev/null +++ b/app/Jobs/RunSyncCommand.php @@ -0,0 +1,64 @@ +toDateTimeString(); + + Cache::put("sync_run.{$this->cacheKey}", [ + 'status' => 'running', + 'output' => null, + 'started_at' => $startedAt, + 'finished_at' => null, + ], now()->addHour()); + + try { + $buffer = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, false); + Artisan::call($this->command, $this->parameters, $buffer); + $output = trim(preg_replace('/\x1b\[[0-9;]*m/', '', $buffer->fetch())); + + Cache::put("sync_run.{$this->cacheKey}", [ + 'status' => 'completed', + 'output' => $output, + 'started_at' => $startedAt, + 'finished_at' => now()->toDateTimeString(), + ], now()->addDay()); + } catch (\Throwable $e) { + Cache::put("sync_run.{$this->cacheKey}", [ + 'status' => 'failed', + 'output' => $e->getMessage(), + 'started_at' => $startedAt, + 'finished_at' => now()->toDateTimeString(), + ], now()->addDay()); + } + } + + public function failed(\Throwable $exception): void + { + Cache::put("sync_run.{$this->cacheKey}", [ + 'status' => 'failed', + 'output' => $exception->getMessage(), + 'started_at' => null, + 'finished_at' => now()->toDateTimeString(), + ], now()->addDay()); + } +} diff --git a/lang/fr/members.php b/lang/fr/members.php index 56f1dd6..8f7e140 100644 --- a/lang/fr/members.php +++ b/lang/fr/members.php @@ -35,7 +35,7 @@ return [ 'widgets' => [ 'stats' => [ 'name' => 'Nouveaux Membres', - 'description' => 'Nombre de nouveaux membres par jour', + 'description' => 'Nombre de nouveaux membres par an', ] ] ], diff --git a/resources/views/filament/pages/partials/sync-heading.blade.php b/resources/views/filament/pages/partials/sync-heading.blade.php new file mode 100644 index 0000000..889b8ec --- /dev/null +++ b/resources/views/filament/pages/partials/sync-heading.blade.php @@ -0,0 +1,10 @@ + + {{ $label }} + @if(in_array($status['status'], ['pending', 'running'])) + + @elseif($status['status'] === 'completed') + + @elseif($status['status'] === 'failed') + + @endif + diff --git a/resources/views/filament/pages/partials/sync-status.blade.php b/resources/views/filament/pages/partials/sync-status.blade.php new file mode 100644 index 0000000..b30dc48 --- /dev/null +++ b/resources/views/filament/pages/partials/sync-status.blade.php @@ -0,0 +1,12 @@ +@if($status['status'] === 'pending') +

En attente dans la file d'exécution...

+@elseif($status['status'] === 'running') +

Exécution en cours...

+@elseif(in_array($status['status'], ['completed', 'failed']) && $status['output']) +
+
{{ $status['output'] }}
+
+ @if($status['finished_at']) +

Terminé à {{ $status['finished_at'] }}

+ @endif +@endif diff --git a/resources/views/filament/pages/synchronisations.blade.php b/resources/views/filament/pages/synchronisations.blade.php new file mode 100644 index 0000000..9ae876b --- /dev/null +++ b/resources/views/filament/pages/synchronisations.blade.php @@ -0,0 +1,109 @@ +@vite('resources/css/backend.css') + + +
hasRunningCommands()) wire:poll.5s @endif + > + @php + $dolibarr = $this->getCommandStatus('dolibarr'); + $expired = $this->getCommandStatus('cleanup_expired'); + $ispMail = $this->getCommandStatus('ispconfig_mail'); + $ispWeb = $this->getCommandStatus('ispconfig_web'); + $nextcloud = $this->getCommandStatus('nextcloud'); + $services = $this->getCommandStatus('services'); + @endphp + + {{-- Dolibarr --}} + + + @include('filament.pages.partials.sync-heading', ['label' => 'Dolibarr', 'status' => $dolibarr]) + +
+

+ Importe les membres et cotisations depuis Dolibarr. +

+ @include('filament.pages.partials.sync-status', ['status' => $dolibarr]) + + {{ $this->getAction('syncDolibarr') }} +
+
+ + {{-- Membres expirés --}} + + + @include('filament.pages.partials.sync-heading', ['label' => 'Membres expirés', 'status' => $expired]) + +
+

+ Désactive les membres expirés dans Dolibarr, ISPConfig et Nextcloud. +

+ @include('filament.pages.partials.sync-status', ['status' => $expired]) + + {{ $this->getAction('cleanupExpired') }} +
+
+ + {{-- ISPConfig Mail --}} + + + @include('filament.pages.partials.sync-heading', ['label' => 'ISPConfig Mail', 'status' => $ispMail]) + +
+

+ Lie les membres à leurs comptes mail ISPConfig (@retzien.fr). +

+ @include('filament.pages.partials.sync-status', ['status' => $ispMail]) + + {{ $this->getAction('syncISPConfigMail') }} +
+
+ + {{-- ISPConfig Web --}} + + + @include('filament.pages.partials.sync-heading', ['label' => 'ISPConfig Web', 'status' => $ispWeb]) + +
+

+ Lie les membres à leurs comptes d'hébergement web. +

+ @include('filament.pages.partials.sync-status', ['status' => $ispWeb]) + + {{ $this->getAction('syncISPConfigWeb') }} +
+
+ + {{-- Nextcloud --}} + + + @include('filament.pages.partials.sync-heading', ['label' => 'Nextcloud', 'status' => $nextcloud]) + +
+

+ Lie les membres à leurs comptes Nextcloud. +

+ @include('filament.pages.partials.sync-status', ['status' => $nextcloud]) + + {{ $this->getAction('syncNextcloud') }} +
+
+ + {{-- Services membres --}} + + + @include('filament.pages.partials.sync-heading', ['label' => 'Services membres', 'status' => $services]) + +
+

+ Synchronise les services associés aux membres actifs. +

+ @include('filament.pages.partials.sync-status', ['status' => $services]) + + {{ $this->getAction('syncServices') }} +
+
+
+ + +