diff --git a/actions/system-info.ts b/actions/system-info.ts new file mode 100644 index 0000000..54c9204 --- /dev/null +++ b/actions/system-info.ts @@ -0,0 +1,16 @@ +'use server'; + +import { SystemInfoService } from '@/services/system-info'; + +export async function getSystemInfo() { + try { + const systemInfo = await SystemInfoService.getSystemInfo(); + return { success: true, data: systemInfo }; + } catch (error) { + console.error('Error getting system info:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to get system info' + }; + } +} diff --git a/components/settings/SettingsIndexPageClient.tsx b/components/settings/SettingsIndexPageClient.tsx index e6519f4..7e55a9e 100644 --- a/components/settings/SettingsIndexPageClient.tsx +++ b/components/settings/SettingsIndexPageClient.tsx @@ -5,12 +5,102 @@ import { Header } from '@/components/ui/Header'; import { Card, CardHeader, CardContent } from '@/components/ui/Card'; import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext'; import Link from 'next/link'; +import { useState, useEffect, useTransition } from 'react'; +import { backupClient } from '@/clients/backup-client'; +import { jiraClient } from '@/clients/jira-client'; +import { getSystemInfo } from '@/actions/system-info'; +import { SystemInfo } from '@/services/system-info'; interface SettingsIndexPageClientProps { initialPreferences: UserPreferences; + initialSystemInfo?: SystemInfo; } -export function SettingsIndexPageClient({ initialPreferences }: SettingsIndexPageClientProps) { +export function SettingsIndexPageClient({ initialPreferences, initialSystemInfo }: SettingsIndexPageClientProps) { + // États pour les actions + const [isBackupLoading, setIsBackupLoading] = useState(false); + const [isJiraTestLoading, setIsJiraTestLoading] = useState(false); + const [systemInfo, setSystemInfo] = useState(initialSystemInfo || null); + const [messages, setMessages] = useState<{ + backup?: { type: 'success' | 'error', text: string }; + jira?: { type: 'success' | 'error', text: string }; + }>({}); + + // useTransition pour le server action + const [isSystemInfoLoading, startTransition] = useTransition(); + + // Fonction pour recharger les infos système (server action) + const loadSystemInfo = () => { + startTransition(async () => { + try { + const result = await getSystemInfo(); + if (result.success && result.data) { + setSystemInfo(result.data); + } else { + console.error('Error loading system info:', result.error); + } + } catch (error) { + console.error('Error loading system info:', error); + } + }); + }; + + // Fonction pour créer une sauvegarde manuelle + const handleCreateBackup = async () => { + setIsBackupLoading(true); + try { + const backup = await backupClient.createBackup(); + setMessages(prev => ({ + ...prev, + backup: { type: 'success', text: `Sauvegarde créée: ${backup.filename}` } + })); + + // Recharger les infos système pour mettre à jour le nombre de sauvegardes + loadSystemInfo(); + } catch { + setMessages(prev => ({ + ...prev, + backup: { type: 'error', text: 'Erreur lors de la création de la sauvegarde' } + })); + } finally { + setIsBackupLoading(false); + } + }; + + // Fonction pour tester la connexion Jira + const handleTestJira = async () => { + setIsJiraTestLoading(true); + try { + const status = await jiraClient.testConnection(); + setMessages(prev => ({ + ...prev, + jira: { + type: status.connected ? 'success' : 'error', + text: status.connected ? 'Connexion Jira réussie !' : `Erreur: ${status.message || 'Connexion échouée'}` + } + })); + } catch { + setMessages(prev => ({ + ...prev, + jira: { type: 'error', text: 'Erreur lors du test de connexion Jira' } + })); + } finally { + setIsJiraTestLoading(false); + } + }; + + // Auto-dismiss des messages après 5 secondes + useEffect(() => { + Object.keys(messages).forEach(key => { + if (messages[key as keyof typeof messages]) { + const timer = setTimeout(() => { + setMessages(prev => ({ ...prev, [key]: undefined })); + }, 5000); + return () => clearTimeout(timer); + } + }); + }, [messages]); + const settingsPages = [ { href: '/settings/general', @@ -63,7 +153,7 @@ export function SettingsIndexPageClient({ initialPreferences }: SettingsIndexPag {/* Quick Stats */} -
+
@@ -82,9 +172,14 @@ export function SettingsIndexPageClient({ initialPreferences }: SettingsIndexPag 🔌

Jira

-

- {initialPreferences.jiraConfig.enabled ? 'Configuré' : 'Non configuré'} -

+
+

+ {initialPreferences.jiraConfig.enabled ? 'Configuré' : 'Non configuré'} +

+ {initialPreferences.jiraConfig.enabled && ( + + )} +
@@ -101,6 +196,20 @@ export function SettingsIndexPageClient({ initialPreferences }: SettingsIndexPag
+ + + +
+ 💾 +
+

Sauvegardes

+

+ {systemInfo ? systemInfo.database.totalBackups : '...'} +

+
+
+
+
{/* Settings Sections */} @@ -168,9 +277,22 @@ export function SettingsIndexPageClient({ initialPreferences }: SettingsIndexPag

Créer une sauvegarde des données

+ {messages.backup && ( +

+ {messages.backup.text} +

+ )} - @@ -184,12 +306,22 @@ export function SettingsIndexPageClient({ initialPreferences }: SettingsIndexPag

Tester la connexion Jira

+ {messages.jira && ( +

+ {messages.jira.text} +

+ )} @@ -200,23 +332,66 @@ export function SettingsIndexPageClient({ initialPreferences }: SettingsIndexPag {/* System Info */} -

ℹ️ Informations système

+
+

ℹ️ Informations système

+ +
-
-
-

Version

-

TowerControl v1.0.0

+ {systemInfo ? ( + <> +
+
+

Version

+

TowerControl v{systemInfo.version}

+
+
+

Dernière maj

+

{systemInfo.lastUpdate}

+
+
+

Environnement

+

{systemInfo.environment}

+
+
+

Uptime

+

{systemInfo.uptime}

+
+
+ +
+

Base de données

+
+
+

Tâches

+

{systemInfo.database.totalTasks}

+
+
+

Utilisateurs

+

{systemInfo.database.totalUsers}

+
+
+

Sauvegardes

+

{systemInfo.database.totalBackups}

+
+
+

Taille DB

+

{systemInfo.database.databaseSize}

+
+
+
+ + ) : ( +
+

Chargement des informations système...

-
-

Dernière maj

-

Il y a 2 jours

-
-
-

Env

-

Development

-
-
+ )}
diff --git a/services/system-info.ts b/services/system-info.ts new file mode 100644 index 0000000..77b8fe1 --- /dev/null +++ b/services/system-info.ts @@ -0,0 +1,191 @@ +import { prisma } from './database'; +import { readFile } from 'fs/promises'; +import { join } from 'path'; + +export interface SystemInfo { + version: string; + environment: string; + database: { + totalTasks: number; + totalUsers: number; + totalBackups: number; + databaseSize: string; + }; + uptime: string; + lastUpdate: string; +} + +export class SystemInfoService { + /** + * Récupère les informations système complètes + */ + static async getSystemInfo(): Promise { + try { + const [packageInfo, dbStats] = await Promise.all([ + this.getPackageInfo(), + this.getDatabaseStats() + ]); + + return { + version: packageInfo.version, + environment: process.env.NODE_ENV || 'development', + database: dbStats, + uptime: this.getUptime(), + lastUpdate: this.getLastUpdate() + }; + } catch (error) { + console.error('Error getting system info:', error); + throw new Error('Failed to get system information'); + } + } + + /** + * Lit les informations du package.json + */ + private static async getPackageInfo(): Promise<{ version: string; name: string }> { + try { + const packagePath = join(process.cwd(), 'package.json'); + const packageContent = await readFile(packagePath, 'utf-8'); + const packageJson = JSON.parse(packageContent); + + return { + name: packageJson.name || 'TowerControl', + version: packageJson.version || '1.0.0' + }; + } catch (error) { + console.error('Error reading package.json:', error); + return { + name: 'TowerControl', + version: '1.0.0' + }; + } + } + + /** + * Récupère les statistiques de la base de données + */ + private static async getDatabaseStats() { + try { + const [totalTasks, totalUsers, totalBackups] = await Promise.all([ + prisma.task.count(), + prisma.userPreferences.count(), + // Pour les backups, on compte les fichiers via le service backup + this.getBackupCount() + ]); + + return { + totalTasks, + totalUsers, + totalBackups, + databaseSize: await this.getDatabaseSize() + }; + } catch (error) { + console.error('Error getting database stats:', error); + return { + totalTasks: 0, + totalUsers: 0, + totalBackups: 0, + databaseSize: 'N/A' + }; + } + } + + /** + * Compte le nombre de sauvegardes + */ + private static async getBackupCount(): Promise { + try { + // Import dynamique pour éviter les dépendances circulaires + const { backupService } = await import('./backup'); + const backups = await backupService.listBackups(); + return backups.length; + } catch (error) { + console.error('Error counting backups:', error); + return 0; + } + } + + /** + * Estime la taille de la base de données + */ + private static async getDatabaseSize(): Promise { + try { + const { stat } = await import('fs/promises'); + const { resolve } = await import('path'); + + // Utiliser la même logique que le service de backup pour trouver la DB + let dbPath: string; + if (process.env.BACKUP_DATABASE_PATH) { + dbPath = resolve(process.cwd(), process.env.BACKUP_DATABASE_PATH); + } else if (process.env.DATABASE_URL) { + dbPath = resolve(process.env.DATABASE_URL.replace('file:', '')); + } else { + dbPath = resolve(process.cwd(), 'prisma', 'dev.db'); + } + + const stats = await stat(dbPath); + + // Convertir en format lisible + const bytes = stats.size; + if (bytes === 0) return '0 B'; + + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; + } catch (error) { + console.error('Error getting database size:', error); + return 'N/A'; + } + } + + /** + * Calcule l'uptime du processus + */ + private static getUptime(): string { + const uptime = process.uptime(); + const hours = Math.floor(uptime / 3600); + const minutes = Math.floor((uptime % 3600) / 60); + const seconds = Math.floor(uptime % 60); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } else if (minutes > 0) { + return `${minutes}m ${seconds}s`; + } else { + return `${seconds}s`; + } + } + + /** + * Retourne une date de dernière mise à jour fictive + * (dans un vrai projet, cela viendrait d'un système de déploiement) + */ + private static getLastUpdate(): string { + // Pour l'instant, on utilise la date de modification du package.json + try { + const fs = require('fs'); + const packagePath = join(process.cwd(), 'package.json'); + const stats = fs.statSync(packagePath); + const now = new Date(); + const lastModified = new Date(stats.mtime); + const diffTime = Math.abs(now.getTime() - lastModified.getTime()); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + if (diffDays === 1) { + return 'Il y a 1 jour'; + } else if (diffDays < 7) { + return `Il y a ${diffDays} jours`; + } else if (diffDays < 30) { + const weeks = Math.floor(diffDays / 7); + return `Il y a ${weeks} semaine${weeks > 1 ? 's' : ''}`; + } else { + const months = Math.floor(diffDays / 30); + return `Il y a ${months} mois`; + } + } catch { + return 'Il y a 2 jours'; + } + } +} diff --git a/src/actions/system-info.ts b/src/actions/system-info.ts new file mode 100644 index 0000000..54c9204 --- /dev/null +++ b/src/actions/system-info.ts @@ -0,0 +1,16 @@ +'use server'; + +import { SystemInfoService } from '@/services/system-info'; + +export async function getSystemInfo() { + try { + const systemInfo = await SystemInfoService.getSystemInfo(); + return { success: true, data: systemInfo }; + } catch (error) { + console.error('Error getting system info:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to get system info' + }; + } +} diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 35fb551..3bc1414 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -1,12 +1,21 @@ import { userPreferencesService } from '@/services/user-preferences'; +import { SystemInfoService } from '@/services/system-info'; import { SettingsIndexPageClient } from '@/components/settings/SettingsIndexPageClient'; // Force dynamic rendering (no static generation) export const dynamic = 'force-dynamic'; export default async function SettingsPage() { - // Fetch basic data for the index page - const preferences = await userPreferencesService.getAllPreferences(); + // Fetch data in parallel for better performance + const [preferences, systemInfo] = await Promise.all([ + userPreferencesService.getAllPreferences(), + SystemInfoService.getSystemInfo() + ]); - return ; + return ( + + ); }