From 2776b50c0627de743482262559798747d362c5a9 Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Fri, 29 May 2026 18:47:55 +0000
Subject: [PATCH 01/14] =?UTF-8?q?Edit=20des=20fichiers=20mod=C3=A8les?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main/java/tg/cyberlabmanager/model/AuditEntry.java | 5 +++++
src/main/java/tg/cyberlabmanager/model/Lab.java | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
index 4c6731f..fa7fa65 100644
--- a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
+++ b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
@@ -8,6 +8,7 @@ public class AuditEntry {
private Integer vmId;
private String action;
private String details;
+ private String labName;
public int getId() {
return id;
@@ -48,4 +49,8 @@ public String getDetails() {
public void setDetails(String details) {
this.details = details;
}
+
+ public String getLog() {
+ return "";
+ }
}
diff --git a/src/main/java/tg/cyberlabmanager/model/Lab.java b/src/main/java/tg/cyberlabmanager/model/Lab.java
index 9e02ad8..9b0522d 100644
--- a/src/main/java/tg/cyberlabmanager/model/Lab.java
+++ b/src/main/java/tg/cyberlabmanager/model/Lab.java
@@ -10,6 +10,7 @@ public class Lab {
private String description;
private String category;
private final List virtualMachines = new ArrayList<>();
+ private String notes;
public Lab() {
}
@@ -53,6 +54,10 @@ public void setCategory(String category) {
this.category = category;
}
+ public String getNotes() { return notes; }
+
+ public void setNotes(String notes) { this.notes = notes; }
+
public List getVirtualMachines() {
return Collections.unmodifiableList(virtualMachines);
}
From 989654a78e80bf711f6271296f7b371eaa9d770c Mon Sep 17 00:00:00 2001
From: akpmarcelin
Date: Fri, 29 May 2026 19:20:06 +0000
Subject: [PATCH 02/14] feat(data): add DatabaseManager, PdfExporter and
temporary model stubs
---
.../cyberlabmanager/data/DatabaseManager.java | 532 +++++++++++++++++-
.../tg/cyberlabmanager/model/AppConfig.java | 26 +-
.../tg/cyberlabmanager/model/AuditEntry.java | 63 +--
.../cyberlabmanager/model/JournalEntry.java | 43 +-
.../java/tg/cyberlabmanager/model/Lab.java | 78 +--
.../tg/cyberlabmanager/model/Snapshot.java | 53 +-
.../tg/cyberlabmanager/model/VMStatus.java | 3 +-
.../cyberlabmanager/model/VirtualMachine.java | 92 +--
.../tg/cyberlabmanager/pdf/PdfExporter.java | 297 +++++++++-
9 files changed, 882 insertions(+), 305 deletions(-)
diff --git a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
index 9cd04a9..56ac366 100644
--- a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
+++ b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
@@ -1,76 +1,562 @@
package tg.cyberlabmanager.data;
-import tg.cyberlabmanager.model.AppConfig;
-import tg.cyberlabmanager.model.AuditEntry;
-import tg.cyberlabmanager.model.JournalEntry;
-import tg.cyberlabmanager.model.Lab;
-import tg.cyberlabmanager.model.Snapshot;
-import tg.cyberlabmanager.model.VirtualMachine;
+import tg.cyberlabmanager.model.*;
+import java.sql.*;
+import java.util.ArrayList;
import java.util.List;
+/**
+ * Gestionnaire de la base de données SQLite de Cyber Lab Manager.
+ * Fournit toutes les opérations CRUD pour les labos, VMs, snapshots,
+ * journal de bord, audit et configuration.
+ *
+ * Utilisation normale : new DatabaseManager("jdbc:sqlite:/home/user/cyberlab.db")
+ * Utilisation en test : new DatabaseManager("jdbc:sqlite::memory:")
+ */
public class DatabaseManager {
+
+ private final String jdbcUrl;
+
+ // =========================================================================
+ // Constructeur + initialisation
+ // =========================================================================
+
+ /**
+ * Crée le DatabaseManager et initialise les tables si elles n'existent pas.
+ *
+ * @param jdbcUrl URL JDBC SQLite, ex: "jdbc:sqlite:/home/user/cyberlab.db"
+ * ou "jdbc:sqlite::memory:" pour les tests
+ */
+ public DatabaseManager(String jdbcUrl) {
+ this.jdbcUrl = jdbcUrl;
+ initDatabase();
+ }
+
+ /**
+ * Ouvre et retourne une connexion SQLite.
+ * Toujours utilisée dans un try-with-resources pour garantir la fermeture.
+ */
+ private Connection getConnection() throws SQLException {
+ return DriverManager.getConnection(jdbcUrl);
+ }
+
+ /**
+ * Crée toutes les tables si elles n'existent pas encore.
+ * Appelé automatiquement au démarrage.
+ */
+ private void initDatabase() {
+ String[] tables = {
+ // Table des laboratoires
+ "CREATE TABLE IF NOT EXISTS labo (" +
+ " id_labo INTEGER PRIMARY KEY AUTOINCREMENT," +
+ " titre TEXT NOT NULL," +
+ " description TEXT," +
+ " categorie TEXT" +
+ ");",
+
+ // Table des machines virtuelles
+ "CREATE TABLE IF NOT EXISTS vm (" +
+ " id_vm INTEGER PRIMARY KEY AUTOINCREMENT," +
+ " nom_vm TEXT NOT NULL," +
+ " uuid TEXT UNIQUE," +
+ " id_labo INTEGER REFERENCES labo(id_labo) ON DELETE SET NULL," +
+ " lien_doc TEXT," +
+ " chemin_vboxmanage TEXT" +
+ ");",
+
+ // Table des snapshots
+ "CREATE TABLE IF NOT EXISTS snapshot (" +
+ " id_snap INTEGER PRIMARY KEY AUTOINCREMENT," +
+ " id_vm INTEGER NOT NULL REFERENCES vm(id_vm) ON DELETE CASCADE," +
+ " nom_snap TEXT," +
+ " date_creation TEXT," +
+ " description TEXT" +
+ ");",
+
+ // Table du journal de bord
+ "CREATE TABLE IF NOT EXISTS journal_entry (" +
+ " id_entry INTEGER PRIMARY KEY AUTOINCREMENT," +
+ " id_vm INTEGER NOT NULL REFERENCES vm(id_vm) ON DELETE CASCADE," +
+ " horodatage TEXT NOT NULL," +
+ " contenu TEXT NOT NULL" +
+ ");",
+
+ // Table d'audit automatique
+ "CREATE TABLE IF NOT EXISTS audit_log (" +
+ " id_log INTEGER PRIMARY KEY AUTOINCREMENT," +
+ " horodatage TEXT NOT NULL," +
+ " id_vm INTEGER REFERENCES vm(id_vm) ON DELETE SET NULL," +
+ " action TEXT NOT NULL," +
+ " details TEXT" +
+ ");",
+
+ // Table de configuration (clé/valeur)
+ "CREATE TABLE IF NOT EXISTS app_config (" +
+ " cle TEXT PRIMARY KEY," +
+ " valeur TEXT" +
+ ");"
+ };
+
+ try (Connection conn = getConnection();
+ Statement stmt = conn.createStatement()) {
+ for (String sql : tables) {
+ stmt.execute(sql);
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Impossible d'initialiser la base de données : " + e.getMessage(), e);
+ }
+ }
+
+ // =========================================================================
+ // LABORATOIRES
+ // =========================================================================
+
+ /**
+ * Sauvegarde un laboratoire. Fait un INSERT si l'id est 0, un UPDATE sinon.
+ * Après INSERT, l'id généré est assigné à l'objet lab.
+ *
+ * @param lab le laboratoire à sauvegarder
+ */
public void saveLab(Lab lab) {
- throw new UnsupportedOperationException("Not implemented yet");
+ if (lab.getId() == 0) {
+ // INSERT
+ String sql = "INSERT INTO labo (titre, description, categorie) VALUES (?, ?, ?)";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ ps.setString(1, lab.getTitre());
+ ps.setString(2, lab.getDescription());
+ ps.setString(3, lab.getCategorie());
+ ps.executeUpdate();
+ ResultSet keys = ps.getGeneratedKeys();
+ if (keys.next()) {
+ lab.setId(keys.getInt(1));
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur saveLab (INSERT) : " + e.getMessage(), e);
+ }
+ } else {
+ // UPDATE
+ String sql = "UPDATE labo SET titre=?, description=?, categorie=? WHERE id_labo=?";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setString(1, lab.getTitre());
+ ps.setString(2, lab.getDescription());
+ ps.setString(3, lab.getCategorie());
+ ps.setInt(4, lab.getId());
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur saveLab (UPDATE) : " + e.getMessage(), e);
+ }
+ }
}
+ /**
+ * Récupère un laboratoire par son identifiant.
+ *
+ * @param id l'identifiant du laboratoire
+ * @return le Lab correspondant, ou null si introuvable
+ */
public Lab getLab(int id) {
- throw new UnsupportedOperationException("Not implemented yet");
+ String sql = "SELECT id_labo, titre, description, categorie FROM labo WHERE id_labo=?";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, id);
+ ResultSet rs = ps.executeQuery();
+ if (rs.next()) {
+ return mapLab(rs);
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur getLab : " + e.getMessage(), e);
+ }
+ return null;
}
+ /**
+ * Retourne tous les laboratoires enregistrés.
+ *
+ * @return liste de tous les Lab, vide si aucun
+ */
public List getAllLabs() {
- throw new UnsupportedOperationException("Not implemented yet");
+ List labs = new ArrayList<>();
+ String sql = "SELECT id_labo, titre, description, categorie FROM labo ORDER BY titre";
+ try (Connection conn = getConnection();
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(sql)) {
+ while (rs.next()) {
+ labs.add(mapLab(rs));
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur getAllLabs : " + e.getMessage(), e);
+ }
+ return labs;
}
+ /**
+ * Supprime un laboratoire. Les VMs associées deviennent orphelines (id_labo → NULL).
+ *
+ * @param lab le laboratoire à supprimer
+ */
public void deleteLab(Lab lab) {
- throw new UnsupportedOperationException("Not implemented yet");
+ String sql = "DELETE FROM labo WHERE id_labo=?";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, lab.getId());
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur deleteLab : " + e.getMessage(), e);
+ }
+ }
+
+ /** Construit un objet Lab depuis un ResultSet. */
+ private Lab mapLab(ResultSet rs) throws SQLException {
+ Lab lab = new Lab();
+ lab.setId(rs.getInt("id_labo"));
+ lab.setTitre(rs.getString("titre"));
+ lab.setDescription(rs.getString("description"));
+ lab.setCategorie(rs.getString("categorie"));
+ return lab;
}
+ // =========================================================================
+ // MACHINES VIRTUELLES
+ // =========================================================================
+
+ /**
+ * Sauvegarde une VM. INSERT si id==0, UPDATE sinon.
+ * Si labId est null, la VM est orpheline (non rattachée à un labo).
+ *
+ * @param vm la machine virtuelle à sauvegarder
+ * @param labId l'identifiant du labo associé, ou null pour une VM orpheline
+ */
public void saveVirtualMachine(VirtualMachine vm, Integer labId) {
- throw new UnsupportedOperationException("Not implemented yet");
+ if (vm.getId() == 0) {
+ String sql = "INSERT INTO vm (nom_vm, uuid, id_labo, lien_doc, chemin_vboxmanage) VALUES (?,?,?,?,?)";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ ps.setString(1, vm.getNomVm());
+ ps.setString(2, vm.getUuid());
+ if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER);
+ ps.setString(4, vm.getLienDoc());
+ ps.setString(5, vm.getCheminVboxmanage());
+ ps.executeUpdate();
+ ResultSet keys = ps.getGeneratedKeys();
+ if (keys.next()) vm.setId(keys.getInt(1));
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur saveVirtualMachine (INSERT) : " + e.getMessage(), e);
+ }
+ } else {
+ String sql = "UPDATE vm SET nom_vm=?, uuid=?, id_labo=?, lien_doc=?, chemin_vboxmanage=? WHERE id_vm=?";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setString(1, vm.getNomVm());
+ ps.setString(2, vm.getUuid());
+ if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER);
+ ps.setString(4, vm.getLienDoc());
+ ps.setString(5, vm.getCheminVboxmanage());
+ ps.setInt(6, vm.getId());
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur saveVirtualMachine (UPDATE) : " + e.getMessage(), e);
+ }
+ }
}
+ /**
+ * Retourne toutes les VMs rattachées à un laboratoire.
+ *
+ * @param labId l'identifiant du laboratoire
+ * @return liste des VMs du labo
+ */
public List getVMsForLab(int labId) {
- throw new UnsupportedOperationException("Not implemented yet");
+ List vms = new ArrayList<>();
+ String sql = "SELECT * FROM vm WHERE id_labo=?";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, labId);
+ ResultSet rs = ps.executeQuery();
+ while (rs.next()) vms.add(mapVM(rs));
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur getVMsForLab : " + e.getMessage(), e);
+ }
+ return vms;
}
+ /**
+ * Retourne les VMs sans laboratoire associé.
+ *
+ * @return liste des VMs orphelines
+ */
public List getOrphanVMs() {
- throw new UnsupportedOperationException("Not implemented yet");
+ List vms = new ArrayList<>();
+ String sql = "SELECT * FROM vm WHERE id_labo IS NULL";
+ try (Connection conn = getConnection();
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(sql)) {
+ while (rs.next()) vms.add(mapVM(rs));
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur getOrphanVMs : " + e.getMessage(), e);
+ }
+ return vms;
}
+ /**
+ * Détache une VM de son laboratoire (id_labo devient NULL).
+ * Ne supprime pas la VM de l'application ni de VirtualBox.
+ *
+ * @param vm la VM à détacher
+ */
public void removeVMFromLab(VirtualMachine vm) {
- throw new UnsupportedOperationException("Not implemented yet");
+ String sql = "UPDATE vm SET id_labo=NULL WHERE id_vm=?";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, vm.getId());
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur removeVMFromLab : " + e.getMessage(), e);
+ }
+ }
+
+ /** Construit un objet VirtualMachine depuis un ResultSet. */
+ private VirtualMachine mapVM(ResultSet rs) throws SQLException {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setId(rs.getInt("id_vm"));
+ vm.setNomVm(rs.getString("nom_vm"));
+ vm.setUuid(rs.getString("uuid"));
+ vm.setLienDoc(rs.getString("lien_doc"));
+ vm.setCheminVboxmanage(rs.getString("chemin_vboxmanage"));
+ return vm;
}
+ // =========================================================================
+ // SNAPSHOTS
+ // =========================================================================
+
+ /**
+ * Enregistre un snapshot associé à une VM.
+ *
+ * @param snap le snapshot à persister
+ * @param vmId l'identifiant de la VM concernée
+ */
public void saveSnapshot(Snapshot snap, int vmId) {
- throw new UnsupportedOperationException("Not implemented yet");
+ String sql = "INSERT INTO snapshot (id_vm, nom_snap, date_creation, description) VALUES (?,?,?,?)";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ ps.setInt(1, vmId);
+ ps.setString(2, snap.getNomSnap());
+ ps.setString(3, snap.getDateCreation());
+ ps.setString(4, snap.getDescription());
+ ps.executeUpdate();
+ ResultSet keys = ps.getGeneratedKeys();
+ if (keys.next()) snap.setId(keys.getInt(1));
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur saveSnapshot : " + e.getMessage(), e);
+ }
}
+ /**
+ * Retourne tous les snapshots d'une VM, du plus récent au plus ancien.
+ *
+ * @param vmId l'identifiant de la VM
+ * @return liste des snapshots
+ */
public List getSnapshotsForVM(int vmId) {
- throw new UnsupportedOperationException("Not implemented yet");
+ List snaps = new ArrayList<>();
+ String sql = "SELECT * FROM snapshot WHERE id_vm=? ORDER BY date_creation DESC";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, vmId);
+ ResultSet rs = ps.executeQuery();
+ while (rs.next()) snaps.add(mapSnapshot(rs));
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur getSnapshotsForVM : " + e.getMessage(), e);
+ }
+ return snaps;
}
+ /** Construit un objet Snapshot depuis un ResultSet. */
+ private Snapshot mapSnapshot(ResultSet rs) throws SQLException {
+ Snapshot s = new Snapshot();
+ s.setId(rs.getInt("id_snap"));
+ s.setNomSnap(rs.getString("nom_snap"));
+ s.setDateCreation(rs.getString("date_creation"));
+ s.setDescription(rs.getString("description"));
+ return s;
+ }
+
+ // =========================================================================
+ // JOURNAL DE BORD
+ // =========================================================================
+
+ /**
+ * Ajoute une entrée dans le journal de bord d'une VM.
+ *
+ * @param entry l'entrée de journal à persister
+ * @param vmId l'identifiant de la VM concernée
+ */
public void addJournalEntry(JournalEntry entry, int vmId) {
- throw new UnsupportedOperationException("Not implemented yet");
+ String sql = "INSERT INTO journal_entry (id_vm, horodatage, contenu) VALUES (?,?,?)";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ ps.setInt(1, vmId);
+ ps.setString(2, entry.getHorodatage());
+ ps.setString(3, entry.getContenu());
+ ps.executeUpdate();
+ ResultSet keys = ps.getGeneratedKeys();
+ if (keys.next()) entry.setId(keys.getInt(1));
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur addJournalEntry : " + e.getMessage(), e);
+ }
}
+ /**
+ * Retourne toutes les entrées du journal d'une VM, de la plus récente à la plus ancienne.
+ *
+ * @param vmId l'identifiant de la VM
+ * @return liste des entrées de journal
+ */
public List getJournalEntriesForVM(int vmId) {
- throw new UnsupportedOperationException("Not implemented yet");
+ List entries = new ArrayList<>();
+ String sql = "SELECT * FROM journal_entry WHERE id_vm=? ORDER BY horodatage DESC";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, vmId);
+ ResultSet rs = ps.executeQuery();
+ while (rs.next()) entries.add(mapJournalEntry(rs));
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur getJournalEntriesForVM : " + e.getMessage(), e);
+ }
+ return entries;
+ }
+
+ /** Construit un objet JournalEntry depuis un ResultSet. */
+ private JournalEntry mapJournalEntry(ResultSet rs) throws SQLException {
+ JournalEntry e = new JournalEntry();
+ e.setId(rs.getInt("id_entry"));
+ e.setHorodatage(rs.getString("horodatage"));
+ e.setContenu(rs.getString("contenu"));
+ return e;
}
+ // =========================================================================
+ // AUDIT
+ // =========================================================================
+
+ /**
+ * Enregistre une entrée d'audit (appelé automatiquement après chaque action sur une VM).
+ *
+ * @param entry l'entrée d'audit à persister
+ */
public void addAuditEntry(AuditEntry entry) {
- throw new UnsupportedOperationException("Not implemented yet");
+ String sql = "INSERT INTO audit_log (horodatage, id_vm, action, details) VALUES (?,?,?,?)";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ ps.setString(1, entry.getHorodatage());
+ if (entry.getVmId() > 0) ps.setInt(2, entry.getVmId()); else ps.setNull(2, Types.INTEGER);
+ ps.setString(3, entry.getAction());
+ ps.setString(4, entry.getDetails());
+ ps.executeUpdate();
+ ResultSet keys = ps.getGeneratedKeys();
+ if (keys.next()) entry.setId(keys.getInt(1));
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur addAuditEntry : " + e.getMessage(), e);
+ }
}
+ /**
+ * Retourne l'historique d'audit d'une VM.
+ *
+ * @param vmId l'identifiant de la VM
+ * @return liste des entrées d'audit, de la plus récente à la plus ancienne
+ */
public List getAuditLogsForVM(int vmId) {
- throw new UnsupportedOperationException("Not implemented yet");
+ List logs = new ArrayList<>();
+ String sql = "SELECT * FROM audit_log WHERE id_vm=? ORDER BY horodatage DESC";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, vmId);
+ ResultSet rs = ps.executeQuery();
+ while (rs.next()) logs.add(mapAuditEntry(rs));
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur getAuditLogsForVM : " + e.getMessage(), e);
+ }
+ return logs;
+ }
+
+ /** Construit un objet AuditEntry depuis un ResultSet. */
+ private AuditEntry mapAuditEntry(ResultSet rs) throws SQLException {
+ AuditEntry a = new AuditEntry();
+ a.setId(rs.getInt("id_log"));
+ a.setHorodatage(rs.getString("horodatage"));
+ a.setVmId(rs.getInt("id_vm"));
+ a.setAction(rs.getString("action"));
+ a.setDetails(rs.getString("details"));
+ return a;
}
+ // =========================================================================
+ // CONFIGURATION
+ // =========================================================================
+
+ /**
+ * Charge la configuration applicative depuis la base.
+ * Retourne une configuration avec des valeurs par défaut si rien n'est enregistré.
+ *
+ * @return la configuration courante
+ */
public AppConfig getConfig() {
- throw new UnsupportedOperationException("Not implemented yet");
+ AppConfig config = new AppConfig();
+ config.setVboxManagePath(getConfigValue("vboxManagePath", "VBoxManage"));
+ config.setDefaultExportDir(getConfigValue("defaultExportDir",
+ System.getProperty("user.home")));
+ return config;
}
+ /**
+ * Sauvegarde la configuration applicative.
+ *
+ * @param config la configuration à persister
+ */
public void saveConfig(AppConfig config) {
- throw new UnsupportedOperationException("Not implemented yet");
+ upsertConfig("vboxManagePath", config.getVboxManagePath());
+ upsertConfig("defaultExportDir", config.getDefaultExportDir());
+ }
+
+ /**
+ * Lit une valeur de configuration par sa clé.
+ *
+ * @param key la clé de configuration
+ * @param defaultValue valeur retournée si la clé est absente
+ * @return la valeur stockée ou la valeur par défaut
+ */
+ private String getConfigValue(String key, String defaultValue) {
+ String sql = "SELECT valeur FROM app_config WHERE cle=?";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setString(1, key);
+ ResultSet rs = ps.executeQuery();
+ if (rs.next()) return rs.getString("valeur");
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur getConfigValue : " + e.getMessage(), e);
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Insère ou met à jour une valeur de configuration (UPSERT).
+ *
+ * @param key la clé
+ * @param value la valeur
+ */
+ private void upsertConfig(String key, String value) {
+ String sql = "INSERT OR REPLACE INTO app_config (cle, valeur) VALUES (?,?)";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setString(1, key);
+ ps.setString(2, value);
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ throw new RuntimeException("Erreur upsertConfig : " + e.getMessage(), e);
+ }
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/tg/cyberlabmanager/model/AppConfig.java b/src/main/java/tg/cyberlabmanager/model/AppConfig.java
index 6d6c891..6ecdfc9 100644
--- a/src/main/java/tg/cyberlabmanager/model/AppConfig.java
+++ b/src/main/java/tg/cyberlabmanager/model/AppConfig.java
@@ -1,22 +1,12 @@
package tg.cyberlabmanager.model;
-
+
public class AppConfig {
private String vboxManagePath;
- private String pdfExportDirectory;
-
- public String getVboxManagePath() {
- return vboxManagePath;
- }
-
- public void setVboxManagePath(String vboxManagePath) {
- this.vboxManagePath = vboxManagePath;
- }
-
- public String getPdfExportDirectory() {
- return pdfExportDirectory;
- }
-
- public void setPdfExportDirectory(String pdfExportDirectory) {
- this.pdfExportDirectory = pdfExportDirectory;
- }
+ private String defaultExportDir;
+
+ public String getVboxManagePath() { return vboxManagePath; }
+ public void setVboxManagePath(String p) { this.vboxManagePath = p; }
+ public String getDefaultExportDir() { return defaultExportDir; }
+ public void setDefaultExportDir(String d) { this.defaultExportDir = d; }
}
+
\ No newline at end of file
diff --git a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
index 4c6731f..27759e1 100644
--- a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
+++ b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
@@ -1,51 +1,20 @@
package tg.cyberlabmanager.model;
-
-import java.time.LocalDateTime;
-
+
public class AuditEntry {
- private int id;
- private LocalDateTime timestamp;
- private Integer vmId;
+ private int id;
+ private String horodatage;
+ private int vmId;
private String action;
private String details;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public LocalDateTime getTimestamp() {
- return timestamp;
- }
-
- public void setTimestamp(LocalDateTime timestamp) {
- this.timestamp = timestamp;
- }
-
- public Integer getVmId() {
- return vmId;
- }
-
- public void setVmId(Integer vmId) {
- this.vmId = vmId;
- }
-
- public String getAction() {
- return action;
- }
-
- public void setAction(String action) {
- this.action = action;
- }
-
- public String getDetails() {
- return details;
- }
-
- public void setDetails(String details) {
- this.details = details;
- }
-}
+
+ public int getId() { return id; }
+ public void setId(int id) { this.id = id; }
+ public String getHorodatage() { return horodatage; }
+ public void setHorodatage(String h){ this.horodatage = h; }
+ public int getVmId() { return vmId; }
+ public void setVmId(int v) { this.vmId = v; }
+ public String getAction() { return action; }
+ public void setAction(String a) { this.action = a; }
+ public String getDetails() { return details; }
+ public void setDetails(String d) { this.details = d; }
+}
\ No newline at end of file
diff --git a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java
index ec7031f..4b1b025 100644
--- a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java
+++ b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java
@@ -1,33 +1,14 @@
package tg.cyberlabmanager.model;
-
-import java.time.LocalDateTime;
-
+
public class JournalEntry {
- private int id;
- private LocalDateTime timestamp;
- private String content;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public LocalDateTime getTimestamp() {
- return timestamp;
- }
-
- public void setTimestamp(LocalDateTime timestamp) {
- this.timestamp = timestamp;
- }
-
- public String getContent() {
- return content;
- }
-
- public void setContent(String content) {
- this.content = content;
- }
-}
+ private int id;
+ private String horodatage;
+ private String contenu;
+
+ public int getId() { return id; }
+ public void setId(int id) { this.id = id; }
+ public String getHorodatage() { return horodatage; }
+ public void setHorodatage(String h){ this.horodatage = h; }
+ public String getContenu() { return contenu; }
+ public void setContenu(String c) { this.contenu = c; }
+}
\ No newline at end of file
diff --git a/src/main/java/tg/cyberlabmanager/model/Lab.java b/src/main/java/tg/cyberlabmanager/model/Lab.java
index 9e02ad8..64ab47d 100644
--- a/src/main/java/tg/cyberlabmanager/model/Lab.java
+++ b/src/main/java/tg/cyberlabmanager/model/Lab.java
@@ -1,67 +1,17 @@
package tg.cyberlabmanager.model;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
+
public class Lab {
- private int id;
- private String title;
+ private int id;
+ private String titre;
private String description;
- private String category;
- private final List virtualMachines = new ArrayList<>();
-
- public Lab() {
- }
-
- public Lab(int id, String title, String description, String category) {
- this.id = id;
- this.title = title;
- this.description = description;
- this.category = category;
- }
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-
- public String getCategory() {
- return category;
- }
-
- public void setCategory(String category) {
- this.category = category;
- }
-
- public List getVirtualMachines() {
- return Collections.unmodifiableList(virtualMachines);
- }
-
- public void addVirtualMachine(VirtualMachine virtualMachine) {
- virtualMachines.add(virtualMachine);
- }
-
- public void removeVirtualMachine(VirtualMachine virtualMachine) {
- virtualMachines.remove(virtualMachine);
- }
-}
+ private String categorie;
+
+ public int getId() { return id; }
+ public void setId(int id) { this.id = id; }
+ public String getTitre() { return titre; }
+ public void setTitre(String t) { this.titre = t; }
+ public String getDescription() { return description; }
+ public void setDescription(String d) { this.description = d; }
+ public String getCategorie() { return categorie; }
+ public void setCategorie(String c) { this.categorie = c; }
+}
\ No newline at end of file
diff --git a/src/main/java/tg/cyberlabmanager/model/Snapshot.java b/src/main/java/tg/cyberlabmanager/model/Snapshot.java
index bd5e879..a4ed027 100644
--- a/src/main/java/tg/cyberlabmanager/model/Snapshot.java
+++ b/src/main/java/tg/cyberlabmanager/model/Snapshot.java
@@ -1,42 +1,17 @@
package tg.cyberlabmanager.model;
-
-import java.time.LocalDateTime;
-
+
public class Snapshot {
- private int id;
- private String name;
- private LocalDateTime createdAt;
+ private int id;
+ private String nomSnap;
+ private String dateCreation;
private String description;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public LocalDateTime getCreatedAt() {
- return createdAt;
- }
-
- public void setCreatedAt(LocalDateTime createdAt) {
- this.createdAt = createdAt;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-}
+
+ public int getId() { return id; }
+ public void setId(int id) { this.id = id; }
+ public String getNomSnap() { return nomSnap; }
+ public void setNomSnap(String n){ this.nomSnap = n; }
+ public String getDateCreation() { return dateCreation; }
+ public void setDateCreation(String d){ this.dateCreation = d; }
+ public String getDescription() { return description; }
+ public void setDescription(String d){ this.description = d; }
+}
\ No newline at end of file
diff --git a/src/main/java/tg/cyberlabmanager/model/VMStatus.java b/src/main/java/tg/cyberlabmanager/model/VMStatus.java
index b6a8744..fa252ef 100644
--- a/src/main/java/tg/cyberlabmanager/model/VMStatus.java
+++ b/src/main/java/tg/cyberlabmanager/model/VMStatus.java
@@ -5,5 +5,6 @@ public enum VMStatus {
POWERED_OFF,
SAVED,
PAUSED,
- UNKNOWN
+ UNKNOWN,
+ ABORTED
}
diff --git a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java
index f031a89..632a857 100644
--- a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java
+++ b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java
@@ -1,71 +1,27 @@
package tg.cyberlabmanager.model;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
+
public class VirtualMachine {
- private int id;
- private String name;
+ private int id;
+ private String nomVm;
private String uuid;
- private String documentationUrl;
- private VMStatus status = VMStatus.UNKNOWN;
- private final List snapshots = new ArrayList<>();
- private final List journalEntries = new ArrayList<>();
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getUuid() {
- return uuid;
- }
-
- public void setUuid(String uuid) {
- this.uuid = uuid;
- }
-
- public String getDocumentationUrl() {
- return documentationUrl;
- }
-
- public void setDocumentationUrl(String documentationUrl) {
- this.documentationUrl = documentationUrl;
- }
-
- public VMStatus getStatus() {
- return status;
- }
-
- public void setStatus(VMStatus status) {
- this.status = status;
- }
-
- public List getSnapshots() {
- return Collections.unmodifiableList(snapshots);
- }
-
- public void addSnapshot(Snapshot snapshot) {
- snapshots.add(snapshot);
- }
-
- public List getJournalEntries() {
- return Collections.unmodifiableList(journalEntries);
- }
-
- public void addJournalEntry(JournalEntry journalEntry) {
- journalEntries.add(journalEntry);
- }
-}
+ private String lienDoc;
+ private String cheminVboxmanage;
+ private VMStatus status;
+
+ public int getId() { return id; }
+ public void setId(int id) { this.id = id; }
+ public String getNomVm() { return nomVm; }
+ public void setNomVm(String n) { this.nomVm = n; }
+ public String getUuid() { return uuid; }
+ public void setUuid(String u) { this.uuid = u; }
+ public String getLienDoc() { return lienDoc; }
+ public void setLienDoc(String l) { this.lienDoc = l; }
+ public String getCheminVboxmanage() { return cheminVboxmanage; }
+ public void setCheminVboxmanage(String c){ this.cheminVboxmanage = c; }
+ public VMStatus getStatus() { return status; }
+ public void setStatus(VMStatus s) { this.status = s; }
+ // Label lisible pour le PDF
+ public String getStatusLabel() {
+ return status != null ? status.name() : "UNKNOWN";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java
index 5c670d7..18490ef 100644
--- a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java
+++ b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java
@@ -1,21 +1,290 @@
package tg.cyberlabmanager.pdf;
-import tg.cyberlabmanager.model.JournalEntry;
-import tg.cyberlabmanager.model.Lab;
-import tg.cyberlabmanager.model.Snapshot;
-import tg.cyberlabmanager.model.VirtualMachine;
+import tg.cyberlabmanager.model.*;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.List;
+/**
+ * Génère un rapport PDF récapitulatif d'un laboratoire Cyber Lab Manager.
+ * Le rapport contient : en-tête du labo, liste des VMs, snapshots et journal de bord.
+ *
+ * Utilise Apache PDFBox 3.x.
+ */
public class PdfExporter {
- public void exportLabToPdf(
- Lab lab,
- List vms,
- List snapshots,
- List journals,
- String filePath
- ) throws IOException {
- throw new UnsupportedOperationException("Not implemented yet");
- }
-}
+
+ // ── Mise en page ──────────────────────────────────────────────────────────
+ private static final float MARGIN = 50f;
+ private static final float PAGE_HEIGHT = PDRectangle.A4.getHeight(); // ~842
+ private static final float PAGE_WIDTH = PDRectangle.A4.getWidth(); // ~595
+ private static final float CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
+
+ // ── Tailles de police ─────────────────────────────────────────────────────
+ private static final float FONT_TITLE = 18f;
+ private static final float FONT_HEADING = 13f;
+ private static final float FONT_SUBHEAD = 11f;
+ private static final float FONT_BODY = 10f;
+
+ // ── Polices PDFBox 3.x ────────────────────────────────────────────────────
+ private static final PDType1Font FONT_BOLD = new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD);
+ private static final PDType1Font FONT_REGULAR = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
+ private static final PDType1Font FONT_OBLIQUE = new PDType1Font(Standard14Fonts.FontName.HELVETICA_OBLIQUE);
+
+ // ── État interne de pagination ─────────────────────────────────────────────
+ private PDDocument document;
+ private PDPageContentStream content;
+ private float cursorY; // position verticale courante (descend vers 0)
+
+ // =========================================================================
+ // Méthode publique principale
+ // =========================================================================
+
+ /**
+ * Génère et sauvegarde le rapport PDF d'un laboratoire.
+ *
+ * @param lab le laboratoire exporté
+ * @param vms liste des VMs du labo (déjà filtrée par le contrôleur)
+ * @param snapshots liste des snapshots (déjà filtrée par le contrôleur)
+ * @param journals liste des entrées de journal (déjà filtrée par le contrôleur)
+ * @param filePath chemin complet du fichier PDF à créer
+ * @throws IOException si l'écriture du fichier échoue
+ */
+ public void exportLabToPdf(Lab lab,
+ List vms,
+ List snapshots,
+ List journals,
+ String filePath) throws IOException {
+
+ document = new PDDocument();
+ newPage(); // crée la première page et initialise cursorY
+
+ // ── En-tête du rapport ────────────────────────────────────────────────
+ writeLine("Rapport – " + lab.getTitre(), FONT_BOLD, FONT_TITLE, true);
+ writeLine("Catégorie : " + nvl(lab.getCategorie(), "Non définie"), FONT_REGULAR, FONT_BODY, false);
+ writeLine("Généré le : " + now(), FONT_REGULAR, FONT_BODY, false);
+ if (lab.getDescription() != null && !lab.getDescription().isBlank()) {
+ writeLine("Description : " + lab.getDescription(), FONT_OBLIQUE, FONT_BODY, false);
+ }
+ spacer(12f);
+ separator();
+ spacer(8f);
+
+ // ── Section 1 : Machines virtuelles ───────────────────────────────────
+ writeLine("1. Machines virtuelles (" + vms.size() + ")", FONT_BOLD, FONT_HEADING, false);
+ spacer(6f);
+
+ if (vms.isEmpty()) {
+ writeLine(" Aucune machine virtuelle dans ce laboratoire.", FONT_OBLIQUE, FONT_BODY, false);
+ } else {
+ for (VirtualMachine vm : vms) {
+ checkPageBreak(60f);
+ writeLine(" ▸ " + vm.getNomVm(), FONT_BOLD, FONT_SUBHEAD, false);
+ writeLine(" UUID : " + nvl(vm.getUuid(), "—"), FONT_REGULAR, FONT_BODY, false);
+ writeLine(" Statut : " + nvl(vm.getStatusLabel(), "Inconnu"), FONT_REGULAR, FONT_BODY, false);
+ if (vm.getLienDoc() != null && !vm.getLienDoc().isBlank()) {
+ writeLine(" Doc : " + vm.getLienDoc(), FONT_REGULAR, FONT_BODY, false);
+ }
+ spacer(5f);
+ }
+ }
+
+ spacer(8f);
+ separator();
+ spacer(8f);
+
+ // ── Section 2 : Snapshots ─────────────────────────────────────────────
+ writeLine("2. Snapshots (" + snapshots.size() + ")", FONT_BOLD, FONT_HEADING, false);
+ spacer(6f);
+
+ if (snapshots.isEmpty()) {
+ writeLine(" Aucun snapshot enregistré.", FONT_OBLIQUE, FONT_BODY, false);
+ } else {
+ for (Snapshot snap : snapshots) {
+ checkPageBreak(40f);
+ writeLine(" ▸ " + nvl(snap.getNomSnap(), "Sans nom"), FONT_BOLD, FONT_SUBHEAD, false);
+ writeLine(" Date : " + nvl(snap.getDateCreation(), "—"), FONT_REGULAR, FONT_BODY, false);
+ if (snap.getDescription() != null && !snap.getDescription().isBlank()) {
+ writeLine(" Note : " + snap.getDescription(), FONT_OBLIQUE, FONT_BODY, false);
+ }
+ spacer(4f);
+ }
+ }
+
+ spacer(8f);
+ separator();
+ spacer(8f);
+
+ // ── Section 3 : Journal de bord ───────────────────────────────────────
+ writeLine("3. Journal de bord (" + journals.size() + " entrée(s))", FONT_BOLD, FONT_HEADING, false);
+ spacer(6f);
+
+ if (journals.isEmpty()) {
+ writeLine(" Aucune note dans le journal.", FONT_OBLIQUE, FONT_BODY, false);
+ } else {
+ for (JournalEntry entry : journals) {
+ checkPageBreak(40f);
+ writeLine(" [" + nvl(entry.getHorodatage(), "—") + "]", FONT_BOLD, FONT_BODY, false);
+ // Le contenu peut être long : on le découpe en lignes
+ writeWrappedText(" " + nvl(entry.getContenu(), ""), FONT_REGULAR, FONT_BODY);
+ spacer(5f);
+ }
+ }
+
+ // ── Pied de page de la dernière page ──────────────────────────────────
+ writeFooter();
+
+ content.close();
+ document.save(filePath);
+ document.close();
+ }
+
+ // =========================================================================
+ // Gestion des pages
+ // =========================================================================
+
+ /**
+ * Crée une nouvelle page A4 et réinitialise le curseur en haut.
+ */
+ private void newPage() throws IOException {
+ if (content != null) {
+ content.close();
+ }
+ PDPage page = new PDPage(PDRectangle.A4);
+ document.addPage(page);
+ content = new PDPageContentStream(document, page);
+ cursorY = PAGE_HEIGHT - MARGIN;
+ }
+
+ /**
+ * Si l'espace restant est insuffisant pour écrire {@code needed} points,
+ * crée une nouvelle page.
+ *
+ * @param needed espace vertical nécessaire en points
+ */
+ private void checkPageBreak(float needed) throws IOException {
+ if (cursorY - needed < MARGIN + 20f) {
+ newPage();
+ }
+ }
+
+ // =========================================================================
+ // Écriture de contenu
+ // =========================================================================
+
+ /**
+ * Écrit une ligne de texte simple et fait descendre le curseur.
+ *
+ * @param text le texte à écrire
+ * @param font la police PDFBox
+ * @param size la taille en points
+ * @param underline true pour ajouter une ligne sous le texte (simulée par un trait)
+ */
+ private void writeLine(String text, PDType1Font font, float size, boolean underline)
+ throws IOException {
+ checkPageBreak(size + 6f);
+ cursorY -= size + 2f;
+
+ content.beginText();
+ content.setFont(font, size);
+ content.newLineAtOffset(MARGIN, cursorY);
+ content.showText(sanitize(text));
+ content.endText();
+
+ if (underline) {
+ float textWidth = font.getStringWidth(sanitize(text)) / 1000 * size;
+ content.moveTo(MARGIN, cursorY - 2f);
+ content.lineTo(MARGIN + textWidth, cursorY - 2f);
+ content.stroke();
+ cursorY -= 4f;
+ }
+ }
+
+ /**
+ * Écrit un texte long en le découpant sur plusieurs lignes si nécessaire.
+ *
+ * @param text le texte (potentiellement long)
+ * @param font la police
+ * @param size la taille
+ */
+ private void writeWrappedText(String text, PDType1Font font, float size) throws IOException {
+ // Largeur max en points, convertie en "unités police"
+ float maxWidth = CONTENT_WIDTH;
+
+ String[] words = sanitize(text).split(" ");
+ StringBuilder line = new StringBuilder();
+
+ for (String word : words) {
+ String candidate = line.isEmpty() ? word : line + " " + word;
+ float w = font.getStringWidth(candidate) / 1000 * size;
+ if (w > maxWidth && !line.isEmpty()) {
+ writeLine(line.toString(), font, size, false);
+ line = new StringBuilder(word);
+ } else {
+ line = new StringBuilder(candidate);
+ }
+ }
+ if (!line.isEmpty()) {
+ writeLine(line.toString(), font, size, false);
+ }
+ }
+
+ /** Ajoute un espace vertical. */
+ private void spacer(float points) {
+ cursorY -= points;
+ }
+
+ /** Dessine un trait de séparation horizontal. */
+ private void separator() throws IOException {
+ content.setLineWidth(0.5f);
+ content.moveTo(MARGIN, cursorY);
+ content.lineTo(PAGE_WIDTH - MARGIN, cursorY);
+ content.stroke();
+ cursorY -= 2f;
+ }
+
+ /** Écrit un pied de page discret en bas de la page courante. */
+ private void writeFooter() throws IOException {
+ float footerY = MARGIN - 10f;
+ content.beginText();
+ content.setFont(FONT_OBLIQUE, 8f);
+ content.newLineAtOffset(MARGIN, footerY);
+ content.showText("Cyber Lab Manager – Rapport généré le " + now());
+ content.endText();
+ }
+
+ // =========================================================================
+ // Utilitaires
+ // =========================================================================
+
+ /** Retourne la valeur ou le fallback si null/blank. */
+ private String nvl(String value, String fallback) {
+ return (value != null && !value.isBlank()) ? value : fallback;
+ }
+
+ /** Date/heure courante formatée pour affichage. */
+ private String now() {
+ return LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"));
+ }
+
+ /**
+ * Nettoie les caractères non supportés par les polices standard PDF (Latin-1 uniquement).
+ * Remplace les caractères hors plage par '?'.
+ */
+ private String sanitize(String text) {
+ if (text == null) return "";
+ StringBuilder sb = new StringBuilder();
+ for (char c : text.toCharArray()) {
+ sb.append(c <= 255 ? c : '?');
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
From c204060251366340a6385c78d12d0eebb1c1d6e9 Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Fri, 29 May 2026 19:51:31 +0000
Subject: [PATCH 03/14] Add lab name to audit schema
---
src/main/resources/tg/cyberlabmanager/data/schema.sql | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/resources/tg/cyberlabmanager/data/schema.sql b/src/main/resources/tg/cyberlabmanager/data/schema.sql
index fde532e..96160eb 100644
--- a/src/main/resources/tg/cyberlabmanager/data/schema.sql
+++ b/src/main/resources/tg/cyberlabmanager/data/schema.sql
@@ -35,6 +35,7 @@ CREATE TABLE IF NOT EXISTS audit_entry (
id_audit INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
id_vm INTEGER,
+ lab_name TEXT,
action TEXT NOT NULL,
details TEXT,
FOREIGN KEY (id_vm) REFERENCES virtual_machine(id_vm) ON DELETE SET NULL
From e6b6e6d15f35cc0ec37125f38dd3ad019c9281d2 Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Fri, 29 May 2026 19:51:55 +0000
Subject: [PATCH 04/14] Align model entities with domain logic
---
.../tg/cyberlabmanager/model/AuditEntry.java | 27 ++++++++++++++++++-
.../java/tg/cyberlabmanager/model/Lab.java | 5 ----
.../tg/cyberlabmanager/model/Snapshot.java | 18 +++++++++++++
3 files changed, 44 insertions(+), 6 deletions(-)
diff --git a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
index fa7fa65..b4fee56 100644
--- a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
+++ b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
@@ -1,8 +1,11 @@
package tg.cyberlabmanager.model;
import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
public class AuditEntry {
+ private static final DateTimeFormatter LOG_TIMESTAMP_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+
private int id;
private LocalDateTime timestamp;
private Integer vmId;
@@ -50,7 +53,29 @@ public void setDetails(String details) {
this.details = details;
}
+ public String getLabName() {
+ return labName;
+ }
+
+ public void setLabName(String labName) {
+ this.labName = labName;
+ }
+
public String getLog() {
- return "";
+ StringBuilder log = new StringBuilder();
+ log.append(timestamp == null ? "unknown-time" : timestamp.format(LOG_TIMESTAMP_FORMATTER));
+ log.append(" | action=").append(action == null ? "UNKNOWN" : action);
+
+ if (vmId != null) {
+ log.append(" | vmId=").append(vmId);
+ }
+ if (labName != null && !labName.isBlank()) {
+ log.append(" | lab=").append(labName);
+ }
+ if (details != null && !details.isBlank()) {
+ log.append(" | details=").append(details);
+ }
+
+ return log.toString();
}
}
diff --git a/src/main/java/tg/cyberlabmanager/model/Lab.java b/src/main/java/tg/cyberlabmanager/model/Lab.java
index 9b0522d..9e02ad8 100644
--- a/src/main/java/tg/cyberlabmanager/model/Lab.java
+++ b/src/main/java/tg/cyberlabmanager/model/Lab.java
@@ -10,7 +10,6 @@ public class Lab {
private String description;
private String category;
private final List virtualMachines = new ArrayList<>();
- private String notes;
public Lab() {
}
@@ -54,10 +53,6 @@ public void setCategory(String category) {
this.category = category;
}
- public String getNotes() { return notes; }
-
- public void setNotes(String notes) { this.notes = notes; }
-
public List getVirtualMachines() {
return Collections.unmodifiableList(virtualMachines);
}
diff --git a/src/main/java/tg/cyberlabmanager/model/Snapshot.java b/src/main/java/tg/cyberlabmanager/model/Snapshot.java
index bd5e879..03eed48 100644
--- a/src/main/java/tg/cyberlabmanager/model/Snapshot.java
+++ b/src/main/java/tg/cyberlabmanager/model/Snapshot.java
@@ -4,9 +4,11 @@
public class Snapshot {
private int id;
+ private String uuid;
private String name;
private LocalDateTime createdAt;
private String description;
+ private boolean online;
public int getId() {
return id;
@@ -16,6 +18,14 @@ public void setId(int id) {
this.id = id;
}
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
public String getName() {
return name;
}
@@ -39,4 +49,12 @@ public String getDescription() {
public void setDescription(String description) {
this.description = description;
}
+
+ public boolean isOnline() {
+ return online;
+ }
+
+ public void setOnline(boolean online) {
+ this.online = online;
+ }
}
From f284e9555088bef15d236bbba73450dd8e9d0386 Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Fri, 29 May 2026 20:11:26 +0000
Subject: [PATCH 05/14] Document public Java APIs with Javadoc
---
src/main/java/module-info.java | 5 +-
.../tg/cyberlabmanager/app/CyberLabApp.java | 15 +++
.../java/tg/cyberlabmanager/app/Launcher.java | 14 +++
.../controller/LabController.java | 79 ++++++++++++++
.../cyberlabmanager/data/DatabaseManager.java | 100 ++++++++++++++++++
.../hypervisor/HypervisorException.java | 14 +++
.../hypervisor/IHypervisor.java | 49 +++++++++
.../hypervisor/VBoxException.java | 22 ++++
.../hypervisor/VBoxOrchestrator.java | 16 +++
.../tg/cyberlabmanager/model/AppConfig.java | 32 ++++++
.../tg/cyberlabmanager/model/AuditEntry.java | 77 ++++++++++++++
.../cyberlabmanager/model/JournalEntry.java | 42 ++++++++
.../java/tg/cyberlabmanager/model/Lab.java | 73 +++++++++++++
.../tg/cyberlabmanager/model/Snapshot.java | 73 +++++++++++++
.../tg/cyberlabmanager/model/VMStatus.java | 8 ++
.../cyberlabmanager/model/VirtualMachine.java | 85 ++++++++++++++-
.../tg/cyberlabmanager/pdf/PdfExporter.java | 19 ++++
.../tg/cyberlabmanager/ui/LabListView.java | 8 ++
.../java/tg/cyberlabmanager/ui/MainView.java | 8 ++
.../tg/cyberlabmanager/ui/VmControlPanel.java | 8 ++
20 files changed, 745 insertions(+), 2 deletions(-)
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 6bcfcc9..930b3ae 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,3 +1,6 @@
+/**
+ * Module principal de l'application Cyber Lab Manager.
+ */
module tg.cyberlabmanager {
// ── JavaFX ──────────────────────────────────────────────────────────
@@ -31,4 +34,4 @@
exports tg.cyberlabmanager.hypervisor;
exports tg.cyberlabmanager.pdf;
exports tg.cyberlabmanager.ui;
-}
\ No newline at end of file
+}
diff --git a/src/main/java/tg/cyberlabmanager/app/CyberLabApp.java b/src/main/java/tg/cyberlabmanager/app/CyberLabApp.java
index 70b6866..d8770e1 100644
--- a/src/main/java/tg/cyberlabmanager/app/CyberLabApp.java
+++ b/src/main/java/tg/cyberlabmanager/app/CyberLabApp.java
@@ -7,7 +7,22 @@
import java.io.IOException;
+/**
+ * Application JavaFX principale de Cyber Lab Manager.
+ */
public class CyberLabApp extends Application {
+ /**
+ * Cree l'application JavaFX.
+ */
+ public CyberLabApp() {
+ }
+
+ /**
+ * Initialise et affiche la fenetre principale JavaFX.
+ *
+ * @param stage scene principale fournie par JavaFX
+ * @throws IOException si le chargement du fichier FXML echoue
+ */
@Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(CyberLabApp.class.getResource("/tg/cyberlabmanager/ui/main-view.fxml"));
diff --git a/src/main/java/tg/cyberlabmanager/app/Launcher.java b/src/main/java/tg/cyberlabmanager/app/Launcher.java
index 7e25325..113aaca 100644
--- a/src/main/java/tg/cyberlabmanager/app/Launcher.java
+++ b/src/main/java/tg/cyberlabmanager/app/Launcher.java
@@ -2,7 +2,21 @@
import javafx.application.Application;
+/**
+ * Point d'entree deleguant le lancement a l'application JavaFX.
+ */
public class Launcher {
+ /**
+ * Cree le lanceur de l'application.
+ */
+ public Launcher() {
+ }
+
+ /**
+ * Lance l'application JavaFX.
+ *
+ * @param args arguments de ligne de commande
+ */
public static void main(String[] args) {
Application.launch(CyberLabApp.class, args);
}
diff --git a/src/main/java/tg/cyberlabmanager/controller/LabController.java b/src/main/java/tg/cyberlabmanager/controller/LabController.java
index 732d284..7228bab 100644
--- a/src/main/java/tg/cyberlabmanager/controller/LabController.java
+++ b/src/main/java/tg/cyberlabmanager/controller/LabController.java
@@ -11,6 +11,9 @@
import java.util.List;
+/**
+ * Controleur principal coordonnant l'interface, la persistance et l'hyperviseur.
+ */
public class LabController {
private IHypervisor hypervisor;
private DatabaseManager databaseManager;
@@ -19,9 +22,19 @@ public class LabController {
@FXML
private Label statusLabel;
+ /**
+ * Cree un controleur vide utilise par JavaFX lors du chargement FXML.
+ */
public LabController() {
}
+ /**
+ * Cree un controleur avec ses dependances injectees.
+ *
+ * @param hypervisor service de pilotage de l'hyperviseur
+ * @param databaseManager service de persistance
+ * @param pdfExporter service d'export PDF
+ */
public LabController(IHypervisor hypervisor, DatabaseManager databaseManager, PdfExporter pdfExporter) {
this.hypervisor = hypervisor;
this.databaseManager = databaseManager;
@@ -35,54 +48,120 @@ private void initialize() {
}
}
+ /**
+ * Declenche le demarrage d'une machine virtuelle.
+ *
+ * @param vm machine virtuelle cible
+ */
public void onStartVMClicked(VirtualMachine vm) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Declenche l'arret d'une machine virtuelle.
+ *
+ * @param vm machine virtuelle cible
+ */
public void onStopVMClicked(VirtualMachine vm) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Declenche la sauvegarde d'etat d'une machine virtuelle.
+ *
+ * @param vm machine virtuelle cible
+ */
public void onSaveStateClicked(VirtualMachine vm) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Declenche la creation d'un snapshot pour une machine virtuelle.
+ *
+ * @param vm machine virtuelle cible
+ * @param name nom du snapshot
+ * @param description description du snapshot
+ */
public void onTakeSnapshotClicked(VirtualMachine vm, String name, String description) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Declenche la restauration d'un snapshot pour une machine virtuelle.
+ *
+ * @param vm machine virtuelle cible
+ * @param snapshotName nom ou identifiant du snapshot
+ */
public void onRestoreSnapshotClicked(VirtualMachine vm, String snapshotName) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Ajoute une note d'analyse a une machine virtuelle.
+ *
+ * @param vm machine virtuelle cible
+ * @param text contenu de la note
+ */
public void onAddJournalEntry(VirtualMachine vm, String text) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Declenche l'import des machines virtuelles disponibles.
+ */
public void onImportVMsClicked() {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Confirme l'import de machines virtuelles selectionnees.
+ *
+ * @param selectedVMs machines virtuelles selectionnees
+ * @param labId identifiant du laboratoire cible, ou {@code null}
+ */
public void onImportConfirm(List selectedVMs, Integer labId) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Declenche l'export PDF d'un laboratoire.
+ *
+ * @param lab laboratoire a exporter
+ */
public void onExportLabClicked(Lab lab) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Declenche l'export des machines virtuelles sans laboratoire.
+ */
public void onExportOrphansClicked() {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Retourne tous les laboratoires connus.
+ *
+ * @return liste des laboratoires
+ */
public List getAllLabs() {
return databaseManager.getAllLabs();
}
+ /**
+ * Retourne les machines virtuelles non rattachees a un laboratoire.
+ *
+ * @return liste des machines virtuelles orphelines
+ */
public List getOrphanVMs() {
return databaseManager.getOrphanVMs();
}
+ /**
+ * Persiste la configuration mise a jour.
+ *
+ * @param newConfig nouvelle configuration applicative
+ */
public void onConfigUpdated(AppConfig newConfig) {
databaseManager.saveConfig(newConfig);
}
diff --git a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
index 9cd04a9..833413f 100644
--- a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
+++ b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
@@ -9,67 +9,167 @@
import java.util.List;
+/**
+ * Service d'acces aux donnees de l'application.
+ *
+ * Il masque la persistance SQLite derriere des operations de haut niveau sur
+ * les entites metier.
+ */
public class DatabaseManager {
+ /**
+ * Cree un gestionnaire de base de donnees.
+ */
+ public DatabaseManager() {
+ }
+
+ /**
+ * Enregistre ou met a jour un laboratoire.
+ *
+ * @param lab laboratoire a persister
+ */
public void saveLab(Lab lab) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Recupere un laboratoire par son identifiant local.
+ *
+ * @param id identifiant local du laboratoire
+ * @return laboratoire correspondant
+ */
public Lab getLab(int id) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Recupere tous les laboratoires.
+ *
+ * @return liste des laboratoires
+ */
public List getAllLabs() {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Supprime un laboratoire sans supprimer les VM physiques.
+ *
+ * @param lab laboratoire a supprimer
+ */
public void deleteLab(Lab lab) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Enregistre ou met a jour une machine virtuelle.
+ *
+ * @param vm machine virtuelle a persister
+ * @param labId identifiant du laboratoire rattache, ou {@code null}
+ */
public void saveVirtualMachine(VirtualMachine vm, Integer labId) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Recupere les machines virtuelles rattachees a un laboratoire.
+ *
+ * @param labId identifiant local du laboratoire
+ * @return liste des machines virtuelles rattachees
+ */
public List getVMsForLab(int labId) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Recupere les machines virtuelles sans laboratoire.
+ *
+ * @return liste des machines virtuelles orphelines
+ */
public List getOrphanVMs() {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Detache une machine virtuelle de son laboratoire.
+ *
+ * @param vm machine virtuelle a detacher
+ */
public void removeVMFromLab(VirtualMachine vm) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Enregistre un snapshot pour une machine virtuelle.
+ *
+ * @param snap snapshot a persister
+ * @param vmId identifiant local de la machine virtuelle
+ */
public void saveSnapshot(Snapshot snap, int vmId) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Recupere les snapshots d'une machine virtuelle.
+ *
+ * @param vmId identifiant local de la machine virtuelle
+ * @return liste des snapshots rattaches
+ */
public List getSnapshotsForVM(int vmId) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Ajoute une note d'analyse pour une machine virtuelle.
+ *
+ * @param entry note d'analyse a persister
+ * @param vmId identifiant local de la machine virtuelle
+ */
public void addJournalEntry(JournalEntry entry, int vmId) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Recupere les notes d'analyse d'une machine virtuelle.
+ *
+ * @param vmId identifiant local de la machine virtuelle
+ * @return liste des notes d'analyse
+ */
public List getJournalEntriesForVM(int vmId) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Ajoute une entree d'audit.
+ *
+ * @param entry entree d'audit a persister
+ */
public void addAuditEntry(AuditEntry entry) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Recupere les entrees d'audit d'une machine virtuelle.
+ *
+ * @param vmId identifiant local de la machine virtuelle
+ * @return liste des entrees d'audit
+ */
public List getAuditLogsForVM(int vmId) {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Recupere la configuration applicative.
+ *
+ * @return configuration applicative
+ */
public AppConfig getConfig() {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /**
+ * Enregistre la configuration applicative.
+ *
+ * @param config configuration a persister
+ */
public void saveConfig(AppConfig config) {
throw new UnsupportedOperationException("Not implemented yet");
}
diff --git a/src/main/java/tg/cyberlabmanager/hypervisor/HypervisorException.java b/src/main/java/tg/cyberlabmanager/hypervisor/HypervisorException.java
index e4c0b3c..d59bf62 100644
--- a/src/main/java/tg/cyberlabmanager/hypervisor/HypervisorException.java
+++ b/src/main/java/tg/cyberlabmanager/hypervisor/HypervisorException.java
@@ -1,10 +1,24 @@
package tg.cyberlabmanager.hypervisor;
+/**
+ * Exception commune aux erreurs d'orchestration d'hyperviseur.
+ */
public class HypervisorException extends Exception {
+ /**
+ * Cree une exception d'hyperviseur avec un message.
+ *
+ * @param message message decrivant l'erreur
+ */
public HypervisorException(String message) {
super(message);
}
+ /**
+ * Cree une exception d'hyperviseur avec un message et une cause.
+ *
+ * @param message message decrivant l'erreur
+ * @param cause cause initiale de l'erreur
+ */
public HypervisorException(String message, Throwable cause) {
super(message, cause);
}
diff --git a/src/main/java/tg/cyberlabmanager/hypervisor/IHypervisor.java b/src/main/java/tg/cyberlabmanager/hypervisor/IHypervisor.java
index c83757b..99ad3cf 100644
--- a/src/main/java/tg/cyberlabmanager/hypervisor/IHypervisor.java
+++ b/src/main/java/tg/cyberlabmanager/hypervisor/IHypervisor.java
@@ -4,18 +4,67 @@
import java.util.List;
+/**
+ * Contrat minimal pour piloter un hyperviseur depuis l'application.
+ */
public interface IHypervisor {
+ /**
+ * Liste les machines virtuelles disponibles dans l'hyperviseur.
+ *
+ * @return liste de couples ou tableaux de donnees representant les VM
+ * @throws HypervisorException si la recuperation echoue
+ */
List listVMs() throws HypervisorException;
+ /**
+ * Retourne le statut courant d'une machine virtuelle.
+ *
+ * @param uuid UUID de la machine virtuelle
+ * @return statut courant de la VM
+ * @throws HypervisorException si le statut ne peut pas etre recupere
+ */
VMStatus getStatus(String uuid) throws HypervisorException;
+ /**
+ * Demarre une machine virtuelle.
+ *
+ * @param uuid UUID de la machine virtuelle
+ * @throws HypervisorException si le demarrage echoue
+ */
void startVM(String uuid) throws HypervisorException;
+ /**
+ * Arrete une machine virtuelle.
+ *
+ * @param uuid UUID de la machine virtuelle
+ * @throws HypervisorException si l'arret echoue
+ */
void stopVM(String uuid) throws HypervisorException;
+ /**
+ * Sauvegarde l'etat d'une machine virtuelle.
+ *
+ * @param uuid UUID de la machine virtuelle
+ * @throws HypervisorException si la sauvegarde d'etat echoue
+ */
void saveState(String uuid) throws HypervisorException;
+ /**
+ * Cree un snapshot pour une machine virtuelle.
+ *
+ * @param uuid UUID de la machine virtuelle
+ * @param name nom du snapshot
+ * @param description description du snapshot
+ * @throws HypervisorException si la creation du snapshot echoue
+ */
void takeSnapshot(String uuid, String name, String description) throws HypervisorException;
+ /**
+ * Restaure un snapshot pour une machine virtuelle.
+ *
+ * @param uuid UUID de la machine virtuelle
+ * @param snapshotName nom ou identifiant du snapshot
+ * @throws HypervisorException si la restauration echoue
+ */
void restoreSnapshot(String uuid, String snapshotName) throws HypervisorException;
}
diff --git a/src/main/java/tg/cyberlabmanager/hypervisor/VBoxException.java b/src/main/java/tg/cyberlabmanager/hypervisor/VBoxException.java
index 7a513e0..9851744 100644
--- a/src/main/java/tg/cyberlabmanager/hypervisor/VBoxException.java
+++ b/src/main/java/tg/cyberlabmanager/hypervisor/VBoxException.java
@@ -1,19 +1,41 @@
package tg.cyberlabmanager.hypervisor;
+/**
+ * Exception specifique aux commandes VirtualBox executees via VBoxManage.
+ */
public class VBoxException extends HypervisorException {
+ /** Code de sortie retourne par VBoxManage. */
private final int exitCode;
+ /** Sortie standard ou erreur retournee par VBoxManage. */
private final String output;
+ /**
+ * Cree une exception VirtualBox avec le code de sortie et la sortie texte.
+ *
+ * @param message message decrivant l'erreur
+ * @param exitCode code de sortie de VBoxManage
+ * @param output sortie standard ou erreur produite par VBoxManage
+ */
public VBoxException(String message, int exitCode, String output) {
super(message);
this.exitCode = exitCode;
this.output = output;
}
+ /**
+ * Retourne le code de sortie de VBoxManage.
+ *
+ * @return code de sortie
+ */
public int getExitCode() {
return exitCode;
}
+ /**
+ * Retourne la sortie produite par VBoxManage.
+ *
+ * @return sortie standard ou erreur
+ */
public String getOutput() {
return output;
}
diff --git a/src/main/java/tg/cyberlabmanager/hypervisor/VBoxOrchestrator.java b/src/main/java/tg/cyberlabmanager/hypervisor/VBoxOrchestrator.java
index 4228933..b8f432d 100644
--- a/src/main/java/tg/cyberlabmanager/hypervisor/VBoxOrchestrator.java
+++ b/src/main/java/tg/cyberlabmanager/hypervisor/VBoxOrchestrator.java
@@ -4,37 +4,53 @@
import java.util.List;
+/**
+ * Implementation de {@link IHypervisor} pour VirtualBox via VBoxManage.
+ */
public class VBoxOrchestrator implements IHypervisor {
+ /**
+ * Cree un orchestrateur VirtualBox.
+ */
+ public VBoxOrchestrator() {
+ }
+
+ /** {@inheritDoc} */
@Override
public List listVMs() throws HypervisorException {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /** {@inheritDoc} */
@Override
public VMStatus getStatus(String uuid) throws HypervisorException {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /** {@inheritDoc} */
@Override
public void startVM(String uuid) throws HypervisorException {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /** {@inheritDoc} */
@Override
public void stopVM(String uuid) throws HypervisorException {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /** {@inheritDoc} */
@Override
public void saveState(String uuid) throws HypervisorException {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /** {@inheritDoc} */
@Override
public void takeSnapshot(String uuid, String name, String description) throws HypervisorException {
throw new UnsupportedOperationException("Not implemented yet");
}
+ /** {@inheritDoc} */
@Override
public void restoreSnapshot(String uuid, String snapshotName) throws HypervisorException {
throw new UnsupportedOperationException("Not implemented yet");
diff --git a/src/main/java/tg/cyberlabmanager/model/AppConfig.java b/src/main/java/tg/cyberlabmanager/model/AppConfig.java
index 6d6c891..04d3f60 100644
--- a/src/main/java/tg/cyberlabmanager/model/AppConfig.java
+++ b/src/main/java/tg/cyberlabmanager/model/AppConfig.java
@@ -1,21 +1,53 @@
package tg.cyberlabmanager.model;
+/**
+ * Configuration persistante de l'application.
+ *
+ * Elle contient les chemins utilises par les services techniques, notamment
+ * VBoxManage et le repertoire d'export PDF.
+ */
public class AppConfig {
private String vboxManagePath;
private String pdfExportDirectory;
+ /**
+ * Cree une configuration vide.
+ */
+ public AppConfig() {
+ }
+
+ /**
+ * Retourne le chemin de l'executable VBoxManage.
+ *
+ * @return chemin configure vers VBoxManage
+ */
public String getVboxManagePath() {
return vboxManagePath;
}
+ /**
+ * Definit le chemin de l'executable VBoxManage.
+ *
+ * @param vboxManagePath chemin vers VBoxManage
+ */
public void setVboxManagePath(String vboxManagePath) {
this.vboxManagePath = vboxManagePath;
}
+ /**
+ * Retourne le repertoire d'export des rapports PDF.
+ *
+ * @return repertoire d'export PDF
+ */
public String getPdfExportDirectory() {
return pdfExportDirectory;
}
+ /**
+ * Definit le repertoire d'export des rapports PDF.
+ *
+ * @param pdfExportDirectory repertoire d'export PDF
+ */
public void setPdfExportDirectory(String pdfExportDirectory) {
this.pdfExportDirectory = pdfExportDirectory;
}
diff --git a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
index b4fee56..03a8ec2 100644
--- a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
+++ b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
@@ -3,6 +3,12 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
+/**
+ * Trace automatique d'une action de controle effectuee sur une machine virtuelle.
+ *
+ * Cette entite est separee de {@link VirtualMachine} afin de conserver un
+ * historique potentiellement volumineux sans alourdir le modele de la VM.
+ */
public class AuditEntry {
private static final DateTimeFormatter LOG_TIMESTAMP_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
@@ -13,54 +19,125 @@ public class AuditEntry {
private String details;
private String labName;
+ /**
+ * Cree une entree d'audit vide.
+ */
+ public AuditEntry() {
+ }
+
+ /**
+ * Retourne l'identifiant local de l'entree d'audit.
+ *
+ * @return identifiant local
+ */
public int getId() {
return id;
}
+ /**
+ * Definit l'identifiant local de l'entree d'audit.
+ *
+ * @param id identifiant local
+ */
public void setId(int id) {
this.id = id;
}
+ /**
+ * Retourne l'horodatage de l'action.
+ *
+ * @return date et heure de l'action
+ */
public LocalDateTime getTimestamp() {
return timestamp;
}
+ /**
+ * Definit l'horodatage de l'action.
+ *
+ * @param timestamp date et heure de l'action
+ */
public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
+ /**
+ * Retourne l'identifiant local de la VM concernee.
+ *
+ * @return identifiant de la VM, ou {@code null} si aucune VM n'est liee
+ */
public Integer getVmId() {
return vmId;
}
+ /**
+ * Definit l'identifiant local de la VM concernee.
+ *
+ * @param vmId identifiant de la VM, ou {@code null}
+ */
public void setVmId(Integer vmId) {
this.vmId = vmId;
}
+ /**
+ * Retourne l'action auditee.
+ *
+ * @return action auditee
+ */
public String getAction() {
return action;
}
+ /**
+ * Definit l'action auditee.
+ *
+ * @param action action auditee
+ */
public void setAction(String action) {
this.action = action;
}
+ /**
+ * Retourne les details de l'action.
+ *
+ * @return details de l'action
+ */
public String getDetails() {
return details;
}
+ /**
+ * Definit les details de l'action.
+ *
+ * @param details details de l'action
+ */
public void setDetails(String details) {
this.details = details;
}
+ /**
+ * Retourne le nom du laboratoire concerne.
+ *
+ * @return nom du laboratoire
+ */
public String getLabName() {
return labName;
}
+ /**
+ * Definit le nom du laboratoire concerne.
+ *
+ * @param labName nom du laboratoire
+ */
public void setLabName(String labName) {
this.labName = labName;
}
+ /**
+ * Construit une representation textuelle de l'entree d'audit.
+ *
+ * @return ligne de log lisible
+ */
public String getLog() {
StringBuilder log = new StringBuilder();
log.append(timestamp == null ? "unknown-time" : timestamp.format(LOG_TIMESTAMP_FORMATTER));
diff --git a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java
index ec7031f..76fca92 100644
--- a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java
+++ b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java
@@ -2,31 +2,73 @@
import java.time.LocalDateTime;
+/**
+ * Note d'analyse horodatee saisie par l'analyste pour une machine virtuelle.
+ *
+ * Une entree de journal est rattachee au cycle de vie logique d'une
+ * {@link VirtualMachine}.
+ */
public class JournalEntry {
private int id;
private LocalDateTime timestamp;
private String content;
+ /**
+ * Cree une entree de journal vide.
+ */
+ public JournalEntry() {
+ }
+
+ /**
+ * Retourne l'identifiant local de l'entree de journal.
+ *
+ * @return identifiant local
+ */
public int getId() {
return id;
}
+ /**
+ * Definit l'identifiant local de l'entree de journal.
+ *
+ * @param id identifiant local
+ */
public void setId(int id) {
this.id = id;
}
+ /**
+ * Retourne l'horodatage de la note.
+ *
+ * @return date et heure de la note
+ */
public LocalDateTime getTimestamp() {
return timestamp;
}
+ /**
+ * Definit l'horodatage de la note.
+ *
+ * @param timestamp date et heure de la note
+ */
public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
+ /**
+ * Retourne le contenu de la note.
+ *
+ * @return contenu saisi par l'analyste
+ */
public String getContent() {
return content;
}
+ /**
+ * Definit le contenu de la note.
+ *
+ * @param content contenu saisi par l'analyste
+ */
public void setContent(String content) {
this.content = content;
}
diff --git a/src/main/java/tg/cyberlabmanager/model/Lab.java b/src/main/java/tg/cyberlabmanager/model/Lab.java
index 9e02ad8..2aac0df 100644
--- a/src/main/java/tg/cyberlabmanager/model/Lab.java
+++ b/src/main/java/tg/cyberlabmanager/model/Lab.java
@@ -4,6 +4,13 @@
import java.util.Collections;
import java.util.List;
+/**
+ * Entite racine representant un laboratoire thematique.
+ *
+ * Ses attributs correspondent aux colonnes de la table {@code lab}. Les VM
+ * rattachees sont exposees en lecture seule et modifiees via les methodes
+ * d'agregation.
+ */
public class Lab {
private int id;
private String title;
@@ -11,9 +18,20 @@ public class Lab {
private String category;
private final List virtualMachines = new ArrayList<>();
+ /**
+ * Cree un laboratoire vide.
+ */
public Lab() {
}
+ /**
+ * Cree un laboratoire avec ses champs principaux.
+ *
+ * @param id identifiant local du laboratoire
+ * @param title titre du laboratoire
+ * @param description description du laboratoire
+ * @param category categorie du laboratoire
+ */
public Lab(int id, String title, String description, String category) {
this.id = id;
this.title = title;
@@ -21,46 +39,101 @@ public Lab(int id, String title, String description, String category) {
this.category = category;
}
+ /**
+ * Retourne l'identifiant local du laboratoire.
+ *
+ * @return identifiant local
+ */
public int getId() {
return id;
}
+ /**
+ * Definit l'identifiant local du laboratoire.
+ *
+ * @param id identifiant local
+ */
public void setId(int id) {
this.id = id;
}
+ /**
+ * Retourne le titre du laboratoire.
+ *
+ * @return titre du laboratoire
+ */
public String getTitle() {
return title;
}
+ /**
+ * Definit le titre du laboratoire.
+ *
+ * @param title titre du laboratoire
+ */
public void setTitle(String title) {
this.title = title;
}
+ /**
+ * Retourne la description du laboratoire.
+ *
+ * @return description du laboratoire
+ */
public String getDescription() {
return description;
}
+ /**
+ * Definit la description du laboratoire.
+ *
+ * @param description description du laboratoire
+ */
public void setDescription(String description) {
this.description = description;
}
+ /**
+ * Retourne la categorie du laboratoire.
+ *
+ * @return categorie du laboratoire
+ */
public String getCategory() {
return category;
}
+ /**
+ * Definit la categorie du laboratoire.
+ *
+ * @param category categorie du laboratoire
+ */
public void setCategory(String category) {
this.category = category;
}
+ /**
+ * Retourne les machines virtuelles rattachees au laboratoire.
+ *
+ * @return liste non modifiable des machines virtuelles
+ */
public List getVirtualMachines() {
return Collections.unmodifiableList(virtualMachines);
}
+ /**
+ * Rattache une machine virtuelle au laboratoire.
+ *
+ * @param virtualMachine machine virtuelle a ajouter
+ */
public void addVirtualMachine(VirtualMachine virtualMachine) {
virtualMachines.add(virtualMachine);
}
+ /**
+ * Retire une machine virtuelle du laboratoire.
+ *
+ * @param virtualMachine machine virtuelle a retirer
+ */
public void removeVirtualMachine(VirtualMachine virtualMachine) {
virtualMachines.remove(virtualMachine);
}
diff --git a/src/main/java/tg/cyberlabmanager/model/Snapshot.java b/src/main/java/tg/cyberlabmanager/model/Snapshot.java
index 03eed48..8b8c396 100644
--- a/src/main/java/tg/cyberlabmanager/model/Snapshot.java
+++ b/src/main/java/tg/cyberlabmanager/model/Snapshot.java
@@ -2,6 +2,13 @@
import java.time.LocalDateTime;
+/**
+ * Instantane VirtualBox rattache a une machine virtuelle.
+ *
+ * Le modele conserve l'identite locale en base, l'UUID VirtualBox, les
+ * metadonnees d'affichage et l'information indiquant si l'instantane a ete pris
+ * pendant que la VM etait en execution.
+ */
public class Snapshot {
private int id;
private String uuid;
@@ -10,50 +17,116 @@ public class Snapshot {
private String description;
private boolean online;
+ /**
+ * Cree un snapshot vide.
+ */
+ public Snapshot() {
+ }
+
+ /**
+ * Retourne l'identifiant local du snapshot.
+ *
+ * @return identifiant local
+ */
public int getId() {
return id;
}
+ /**
+ * Definit l'identifiant local du snapshot.
+ *
+ * @param id identifiant local
+ */
public void setId(int id) {
this.id = id;
}
+ /**
+ * Retourne l'UUID VirtualBox du snapshot.
+ *
+ * @return UUID VirtualBox
+ */
public String getUuid() {
return uuid;
}
+ /**
+ * Definit l'UUID VirtualBox du snapshot.
+ *
+ * @param uuid UUID VirtualBox
+ */
public void setUuid(String uuid) {
this.uuid = uuid;
}
+ /**
+ * Retourne le nom du snapshot.
+ *
+ * @return nom du snapshot
+ */
public String getName() {
return name;
}
+ /**
+ * Definit le nom du snapshot.
+ *
+ * @param name nom du snapshot
+ */
public void setName(String name) {
this.name = name;
}
+ /**
+ * Retourne la date de creation du snapshot.
+ *
+ * @return date de creation
+ */
public LocalDateTime getCreatedAt() {
return createdAt;
}
+ /**
+ * Definit la date de creation du snapshot.
+ *
+ * @param createdAt date de creation
+ */
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
+ /**
+ * Retourne la description du snapshot.
+ *
+ * @return description du snapshot
+ */
public String getDescription() {
return description;
}
+ /**
+ * Definit la description du snapshot.
+ *
+ * @param description description du snapshot
+ */
public void setDescription(String description) {
this.description = description;
}
+ /**
+ * Indique si le snapshot a ete pris VM allumee.
+ *
+ * @return {@code true} si le snapshot inclut l'etat en execution
+ */
public boolean isOnline() {
return online;
}
+ /**
+ * Definit si le snapshot a ete pris VM allumee.
+ *
+ * @param online {@code true} si le snapshot inclut l'etat en execution
+ */
public void setOnline(boolean online) {
this.online = online;
}
diff --git a/src/main/java/tg/cyberlabmanager/model/VMStatus.java b/src/main/java/tg/cyberlabmanager/model/VMStatus.java
index b6a8744..b014192 100644
--- a/src/main/java/tg/cyberlabmanager/model/VMStatus.java
+++ b/src/main/java/tg/cyberlabmanager/model/VMStatus.java
@@ -1,9 +1,17 @@
package tg.cyberlabmanager.model;
+/**
+ * Etats stables d'une machine virtuelle tels qu'exposes par l'hyperviseur.
+ */
public enum VMStatus {
+ /** La VM est en cours d'execution. */
RUNNING,
+ /** La VM est eteinte. */
POWERED_OFF,
+ /** La VM est dans un etat sauvegarde. */
SAVED,
+ /** La VM est en pause. */
PAUSED,
+ /** Le statut de la VM n'a pas pu etre determine. */
UNKNOWN
}
diff --git a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java
index f031a89..18d8f75 100644
--- a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java
+++ b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java
@@ -4,6 +4,13 @@
import java.util.Collections;
import java.util.List;
+/**
+ * Machine virtuelle importee depuis VirtualBox.
+ *
+ * L'UUID VirtualBox fournit une reference stable. Les snapshots et notes
+ * d'analyse sont encapsules pour eviter l'exposition directe des listes
+ * internes.
+ */
public class VirtualMachine {
private int id;
private String name;
@@ -13,59 +20,135 @@ public class VirtualMachine {
private final List snapshots = new ArrayList<>();
private final List journalEntries = new ArrayList<>();
+ /**
+ * Cree une machine virtuelle vide.
+ */
+ public VirtualMachine() {
+ }
+
+ /**
+ * Retourne l'identifiant local de la VM.
+ *
+ * @return identifiant local
+ */
public int getId() {
return id;
}
+ /**
+ * Definit l'identifiant local de la VM.
+ *
+ * @param id identifiant local
+ */
public void setId(int id) {
this.id = id;
}
+ /**
+ * Retourne le nom de la VM.
+ *
+ * @return nom de la VM
+ */
public String getName() {
return name;
}
+ /**
+ * Definit le nom de la VM.
+ *
+ * @param name nom de la VM
+ */
public void setName(String name) {
this.name = name;
}
+ /**
+ * Retourne l'UUID VirtualBox de la VM.
+ *
+ * @return UUID VirtualBox
+ */
public String getUuid() {
return uuid;
}
+ /**
+ * Definit l'UUID VirtualBox de la VM.
+ *
+ * @param uuid UUID VirtualBox
+ */
public void setUuid(String uuid) {
this.uuid = uuid;
}
+ /**
+ * Retourne l'URL de documentation associee a la VM.
+ *
+ * @return URL de documentation
+ */
public String getDocumentationUrl() {
return documentationUrl;
}
+ /**
+ * Definit l'URL de documentation associee a la VM.
+ *
+ * @param documentationUrl URL de documentation
+ */
public void setDocumentationUrl(String documentationUrl) {
this.documentationUrl = documentationUrl;
}
+ /**
+ * Retourne le statut courant de la VM.
+ *
+ * @return statut de la VM
+ */
public VMStatus getStatus() {
return status;
}
+ /**
+ * Definit le statut courant de la VM.
+ *
+ * @param status statut de la VM
+ */
public void setStatus(VMStatus status) {
this.status = status;
}
+ /**
+ * Retourne les snapshots rattaches a la VM.
+ *
+ * @return liste non modifiable des snapshots
+ */
public List getSnapshots() {
return Collections.unmodifiableList(snapshots);
}
+ /**
+ * Ajoute un snapshot a la VM.
+ *
+ * @param snapshot snapshot a ajouter
+ */
public void addSnapshot(Snapshot snapshot) {
snapshots.add(snapshot);
}
+ /**
+ * Retourne les notes d'analyse rattachees a la VM.
+ *
+ * @return liste non modifiable des notes d'analyse
+ */
public List getJournalEntries() {
return Collections.unmodifiableList(journalEntries);
}
- public void addJournalEntry(JournalEntry journalEntry) {
+ /**
+ * Ajoute une note d'analyse a la VM.
+ *
+ * @param journalEntry note d'analyse a ajouter
+ */
+ public void addJournalEntry(JournalEntry journalEntry) {
journalEntries.add(journalEntry);
}
}
diff --git a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java
index 5c670d7..2ffbcb6 100644
--- a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java
+++ b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java
@@ -8,7 +8,26 @@
import java.io.IOException;
import java.util.List;
+/**
+ * Service responsable de l'export d'un laboratoire au format PDF.
+ */
public class PdfExporter {
+ /**
+ * Cree un exporteur PDF.
+ */
+ public PdfExporter() {
+ }
+
+ /**
+ * Exporte les donnees d'un laboratoire dans un fichier PDF.
+ *
+ * @param lab laboratoire exporte
+ * @param vms machines virtuelles du laboratoire
+ * @param snapshots snapshots a inclure
+ * @param journals notes d'analyse a inclure
+ * @param filePath chemin du fichier PDF cible
+ * @throws IOException si l'ecriture du PDF echoue
+ */
public void exportLabToPdf(
Lab lab,
List vms,
diff --git a/src/main/java/tg/cyberlabmanager/ui/LabListView.java b/src/main/java/tg/cyberlabmanager/ui/LabListView.java
index 7a10e42..8e06fb6 100644
--- a/src/main/java/tg/cyberlabmanager/ui/LabListView.java
+++ b/src/main/java/tg/cyberlabmanager/ui/LabListView.java
@@ -1,4 +1,12 @@
package tg.cyberlabmanager.ui;
+/**
+ * Vue chargee d'afficher la liste des laboratoires.
+ */
public class LabListView {
+ /**
+ * Cree la vue de liste des laboratoires.
+ */
+ public LabListView() {
+ }
}
diff --git a/src/main/java/tg/cyberlabmanager/ui/MainView.java b/src/main/java/tg/cyberlabmanager/ui/MainView.java
index 0e763a1..b38c414 100644
--- a/src/main/java/tg/cyberlabmanager/ui/MainView.java
+++ b/src/main/java/tg/cyberlabmanager/ui/MainView.java
@@ -1,4 +1,12 @@
package tg.cyberlabmanager.ui;
+/**
+ * Vue principale de l'application.
+ */
public class MainView {
+ /**
+ * Cree la vue principale.
+ */
+ public MainView() {
+ }
}
diff --git a/src/main/java/tg/cyberlabmanager/ui/VmControlPanel.java b/src/main/java/tg/cyberlabmanager/ui/VmControlPanel.java
index 49b4eea..e0692bc 100644
--- a/src/main/java/tg/cyberlabmanager/ui/VmControlPanel.java
+++ b/src/main/java/tg/cyberlabmanager/ui/VmControlPanel.java
@@ -1,4 +1,12 @@
package tg.cyberlabmanager.ui;
+/**
+ * Vue chargee des controles utilisateur pour les machines virtuelles.
+ */
public class VmControlPanel {
+ /**
+ * Cree le panneau de controle des machines virtuelles.
+ */
+ public VmControlPanel() {
+ }
}
From daaddcb8aecf0b69ab795427a5e2b2b69315746f Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Fri, 29 May 2026 20:25:53 +0000
Subject: [PATCH 06/14] Add unit tests for model classes
---
.../java/tg/cyberlabmanager/model/.gitkeep | 1 -
.../cyberlabmanager/model/AppConfigTest.java | 18 ++++++
.../cyberlabmanager/model/AuditEntryTest.java | 51 +++++++++++++++
.../model/JournalEntryTest.java | 23 +++++++
.../tg/cyberlabmanager/model/LabTest.java | 64 +++++++++++++++++++
.../cyberlabmanager/model/SnapshotTest.java | 30 +++++++++
.../cyberlabmanager/model/VMStatusTest.java | 18 ++++++
.../model/VirtualMachineTest.java | 61 ++++++++++++++++++
8 files changed, 265 insertions(+), 1 deletion(-)
delete mode 100644 src/test/java/tg/cyberlabmanager/model/.gitkeep
create mode 100644 src/test/java/tg/cyberlabmanager/model/AppConfigTest.java
create mode 100644 src/test/java/tg/cyberlabmanager/model/AuditEntryTest.java
create mode 100644 src/test/java/tg/cyberlabmanager/model/JournalEntryTest.java
create mode 100644 src/test/java/tg/cyberlabmanager/model/LabTest.java
create mode 100644 src/test/java/tg/cyberlabmanager/model/SnapshotTest.java
create mode 100644 src/test/java/tg/cyberlabmanager/model/VMStatusTest.java
create mode 100644 src/test/java/tg/cyberlabmanager/model/VirtualMachineTest.java
diff --git a/src/test/java/tg/cyberlabmanager/model/.gitkeep b/src/test/java/tg/cyberlabmanager/model/.gitkeep
deleted file mode 100644
index 8b13789..0000000
--- a/src/test/java/tg/cyberlabmanager/model/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/test/java/tg/cyberlabmanager/model/AppConfigTest.java b/src/test/java/tg/cyberlabmanager/model/AppConfigTest.java
new file mode 100644
index 0000000..5b24c55
--- /dev/null
+++ b/src/test/java/tg/cyberlabmanager/model/AppConfigTest.java
@@ -0,0 +1,18 @@
+package tg.cyberlabmanager.model;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class AppConfigTest {
+ @Test
+ void shouldStoreConfigurationPaths() {
+ AppConfig config = new AppConfig();
+
+ config.setVboxManagePath("/usr/bin/VBoxManage");
+ config.setPdfExportDirectory("/tmp/reports");
+
+ assertEquals("/usr/bin/VBoxManage", config.getVboxManagePath());
+ assertEquals("/tmp/reports", config.getPdfExportDirectory());
+ }
+}
diff --git a/src/test/java/tg/cyberlabmanager/model/AuditEntryTest.java b/src/test/java/tg/cyberlabmanager/model/AuditEntryTest.java
new file mode 100644
index 0000000..493d86a
--- /dev/null
+++ b/src/test/java/tg/cyberlabmanager/model/AuditEntryTest.java
@@ -0,0 +1,51 @@
+package tg.cyberlabmanager.model;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class AuditEntryTest {
+ @Test
+ void shouldStoreAuditFields() {
+ LocalDateTime timestamp = LocalDateTime.of(2026, 5, 29, 20, 30);
+ AuditEntry entry = new AuditEntry();
+
+ entry.setId(7);
+ entry.setTimestamp(timestamp);
+ entry.setVmId(12);
+ entry.setAction("START");
+ entry.setDetails("VM started from UI");
+ entry.setLabName("Malware Lab");
+
+ assertEquals(7, entry.getId());
+ assertEquals(timestamp, entry.getTimestamp());
+ assertEquals(12, entry.getVmId());
+ assertEquals("START", entry.getAction());
+ assertEquals("VM started from UI", entry.getDetails());
+ assertEquals("Malware Lab", entry.getLabName());
+ }
+
+ @Test
+ void shouldBuildReadableLogLine() {
+ AuditEntry entry = new AuditEntry();
+ entry.setTimestamp(LocalDateTime.of(2026, 5, 29, 20, 30));
+ entry.setVmId(12);
+ entry.setAction("SNAPSHOT_TAKEN");
+ entry.setLabName("Pentest Lab");
+ entry.setDetails("Snapshot baseline created");
+
+ assertEquals(
+ "2026-05-29T20:30:00 | action=SNAPSHOT_TAKEN | vmId=12 | lab=Pentest Lab | details=Snapshot baseline created",
+ entry.getLog()
+ );
+ }
+
+ @Test
+ void shouldBuildFallbackLogWhenOptionalFieldsAreMissing() {
+ AuditEntry entry = new AuditEntry();
+
+ assertEquals("unknown-time | action=UNKNOWN", entry.getLog());
+ }
+}
diff --git a/src/test/java/tg/cyberlabmanager/model/JournalEntryTest.java b/src/test/java/tg/cyberlabmanager/model/JournalEntryTest.java
new file mode 100644
index 0000000..1abd563
--- /dev/null
+++ b/src/test/java/tg/cyberlabmanager/model/JournalEntryTest.java
@@ -0,0 +1,23 @@
+package tg.cyberlabmanager.model;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class JournalEntryTest {
+ @Test
+ void shouldStoreJournalEntryFields() {
+ LocalDateTime timestamp = LocalDateTime.of(2026, 5, 29, 21, 15);
+ JournalEntry entry = new JournalEntry();
+
+ entry.setId(3);
+ entry.setTimestamp(timestamp);
+ entry.setContent("Observed suspicious network traffic.");
+
+ assertEquals(3, entry.getId());
+ assertEquals(timestamp, entry.getTimestamp());
+ assertEquals("Observed suspicious network traffic.", entry.getContent());
+ }
+}
diff --git a/src/test/java/tg/cyberlabmanager/model/LabTest.java b/src/test/java/tg/cyberlabmanager/model/LabTest.java
new file mode 100644
index 0000000..e8d5bc8
--- /dev/null
+++ b/src/test/java/tg/cyberlabmanager/model/LabTest.java
@@ -0,0 +1,64 @@
+package tg.cyberlabmanager.model;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class LabTest {
+ @Test
+ void shouldCreateLabWithConstructorValues() {
+ Lab lab = new Lab(1, "Malware Analysis", "Static and dynamic malware lab", "malware");
+
+ assertEquals(1, lab.getId());
+ assertEquals("Malware Analysis", lab.getTitle());
+ assertEquals("Static and dynamic malware lab", lab.getDescription());
+ assertEquals("malware", lab.getCategory());
+ }
+
+ @Test
+ void shouldUpdateLabFieldsWithSetters() {
+ Lab lab = new Lab();
+
+ lab.setId(2);
+ lab.setTitle("Pentest");
+ lab.setDescription("Web attack lab");
+ lab.setCategory("pentest");
+
+ assertEquals(2, lab.getId());
+ assertEquals("Pentest", lab.getTitle());
+ assertEquals("Web attack lab", lab.getDescription());
+ assertEquals("pentest", lab.getCategory());
+ }
+
+ @Test
+ void shouldAddAndRemoveVirtualMachines() {
+ Lab lab = new Lab();
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("Kali");
+
+ lab.addVirtualMachine(vm);
+
+ List virtualMachines = lab.getVirtualMachines();
+ assertEquals(1, virtualMachines.size());
+ assertSame(vm, virtualMachines.get(0));
+
+ lab.removeVirtualMachine(vm);
+
+ assertTrue(lab.getVirtualMachines().isEmpty());
+ }
+
+ @Test
+ void shouldExposeUnmodifiableVirtualMachineList() {
+ Lab lab = new Lab();
+
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> lab.getVirtualMachines().add(new VirtualMachine())
+ );
+ }
+}
diff --git a/src/test/java/tg/cyberlabmanager/model/SnapshotTest.java b/src/test/java/tg/cyberlabmanager/model/SnapshotTest.java
new file mode 100644
index 0000000..9501f63
--- /dev/null
+++ b/src/test/java/tg/cyberlabmanager/model/SnapshotTest.java
@@ -0,0 +1,30 @@
+package tg.cyberlabmanager.model;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class SnapshotTest {
+ @Test
+ void shouldStoreSnapshotFields() {
+ LocalDateTime createdAt = LocalDateTime.of(2026, 5, 29, 22, 0);
+ Snapshot snapshot = new Snapshot();
+
+ snapshot.setId(4);
+ snapshot.setUuid("snap-uuid-123");
+ snapshot.setName("baseline");
+ snapshot.setCreatedAt(createdAt);
+ snapshot.setDescription("Clean VM state");
+ snapshot.setOnline(true);
+
+ assertEquals(4, snapshot.getId());
+ assertEquals("snap-uuid-123", snapshot.getUuid());
+ assertEquals("baseline", snapshot.getName());
+ assertEquals(createdAt, snapshot.getCreatedAt());
+ assertEquals("Clean VM state", snapshot.getDescription());
+ assertTrue(snapshot.isOnline());
+ }
+}
diff --git a/src/test/java/tg/cyberlabmanager/model/VMStatusTest.java b/src/test/java/tg/cyberlabmanager/model/VMStatusTest.java
new file mode 100644
index 0000000..5f86ffe
--- /dev/null
+++ b/src/test/java/tg/cyberlabmanager/model/VMStatusTest.java
@@ -0,0 +1,18 @@
+package tg.cyberlabmanager.model;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+class VMStatusTest {
+ @Test
+ void shouldExposeExpectedStableStatuses() {
+ assertEquals(5, VMStatus.values().length);
+ assertSame(VMStatus.RUNNING, VMStatus.valueOf("RUNNING"));
+ assertSame(VMStatus.POWERED_OFF, VMStatus.valueOf("POWERED_OFF"));
+ assertSame(VMStatus.SAVED, VMStatus.valueOf("SAVED"));
+ assertSame(VMStatus.PAUSED, VMStatus.valueOf("PAUSED"));
+ assertSame(VMStatus.UNKNOWN, VMStatus.valueOf("UNKNOWN"));
+ }
+}
diff --git a/src/test/java/tg/cyberlabmanager/model/VirtualMachineTest.java b/src/test/java/tg/cyberlabmanager/model/VirtualMachineTest.java
new file mode 100644
index 0000000..c986258
--- /dev/null
+++ b/src/test/java/tg/cyberlabmanager/model/VirtualMachineTest.java
@@ -0,0 +1,61 @@
+package tg.cyberlabmanager.model;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class VirtualMachineTest {
+ @Test
+ void shouldUseUnknownStatusByDefault() {
+ VirtualMachine vm = new VirtualMachine();
+
+ assertSame(VMStatus.UNKNOWN, vm.getStatus());
+ }
+
+ @Test
+ void shouldStoreVirtualMachineFields() {
+ VirtualMachine vm = new VirtualMachine();
+
+ vm.setId(5);
+ vm.setName("Ubuntu Lab");
+ vm.setUuid("vm-uuid-456");
+ vm.setDocumentationUrl("https://example.test/doc");
+ vm.setStatus(VMStatus.RUNNING);
+
+ assertEquals(5, vm.getId());
+ assertEquals("Ubuntu Lab", vm.getName());
+ assertEquals("vm-uuid-456", vm.getUuid());
+ assertEquals("https://example.test/doc", vm.getDocumentationUrl());
+ assertSame(VMStatus.RUNNING, vm.getStatus());
+ }
+
+ @Test
+ void shouldAddSnapshotsAndJournalEntries() {
+ VirtualMachine vm = new VirtualMachine();
+ Snapshot snapshot = new Snapshot();
+ JournalEntry journalEntry = new JournalEntry();
+
+ vm.addSnapshot(snapshot);
+ vm.addJournalEntry(journalEntry);
+
+ List snapshots = vm.getSnapshots();
+ List journalEntries = vm.getJournalEntries();
+
+ assertEquals(1, snapshots.size());
+ assertSame(snapshot, snapshots.get(0));
+ assertEquals(1, journalEntries.size());
+ assertSame(journalEntry, journalEntries.get(0));
+ }
+
+ @Test
+ void shouldExposeUnmodifiableCollections() {
+ VirtualMachine vm = new VirtualMachine();
+
+ assertThrows(UnsupportedOperationException.class, () -> vm.getSnapshots().add(new Snapshot()));
+ assertThrows(UnsupportedOperationException.class, () -> vm.getJournalEntries().add(new JournalEntry()));
+ }
+}
From 9d130aa38bcbf5a3366a59f2e92759b9bc479657 Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Sat, 30 May 2026 02:09:29 +0000
Subject: [PATCH 07/14] Corrections mineures orthographe du JavaDoc
---
.../tg/cyberlabmanager/model/AppConfig.java | 20 ++++-----
.../tg/cyberlabmanager/model/AuditEntry.java | 42 +++++++++----------
.../cyberlabmanager/model/JournalEntry.java | 14 +++----
.../java/tg/cyberlabmanager/model/Lab.java | 32 +++++++-------
.../tg/cyberlabmanager/model/Snapshot.java | 34 +++++++--------
.../tg/cyberlabmanager/model/VMStatus.java | 10 ++---
.../cyberlabmanager/model/VirtualMachine.java | 34 +++++++--------
7 files changed, 93 insertions(+), 93 deletions(-)
diff --git a/src/main/java/tg/cyberlabmanager/model/AppConfig.java b/src/main/java/tg/cyberlabmanager/model/AppConfig.java
index 04d3f60..2a3fa9a 100644
--- a/src/main/java/tg/cyberlabmanager/model/AppConfig.java
+++ b/src/main/java/tg/cyberlabmanager/model/AppConfig.java
@@ -3,30 +3,30 @@
/**
* Configuration persistante de l'application.
*
- * Elle contient les chemins utilises par les services techniques, notamment
- * VBoxManage et le repertoire d'export PDF.
+ * Elle contient les chemins utilisés par les services techniques, notamment
+ * VBoxManage et le répertoire d'export PDF.
*/
public class AppConfig {
private String vboxManagePath;
private String pdfExportDirectory;
/**
- * Cree une configuration vide.
+ * Crée une configuration vide.
*/
public AppConfig() {
}
/**
- * Retourne le chemin de l'executable VBoxManage.
+ * Retourne le chemin de l'exécutable VBoxManage.
*
- * @return chemin configure vers VBoxManage
+ * @return chemin configuré vers VBoxManage
*/
public String getVboxManagePath() {
return vboxManagePath;
}
/**
- * Definit le chemin de l'executable VBoxManage.
+ * Définit le chemin de l'exécutable VBoxManage.
*
* @param vboxManagePath chemin vers VBoxManage
*/
@@ -35,18 +35,18 @@ public void setVboxManagePath(String vboxManagePath) {
}
/**
- * Retourne le repertoire d'export des rapports PDF.
+ * Retourne le répertoire d'export des rapports PDF.
*
- * @return repertoire d'export PDF
+ * @return répertoire d'export PDF
*/
public String getPdfExportDirectory() {
return pdfExportDirectory;
}
/**
- * Definit le repertoire d'export des rapports PDF.
+ * Définit le répertoire d'export des rapports PDF.
*
- * @param pdfExportDirectory repertoire d'export PDF
+ * @param pdfExportDirectory répertoire d'export PDF
*/
public void setPdfExportDirectory(String pdfExportDirectory) {
this.pdfExportDirectory = pdfExportDirectory;
diff --git a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
index 03a8ec2..7c87fea 100644
--- a/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
+++ b/src/main/java/tg/cyberlabmanager/model/AuditEntry.java
@@ -4,10 +4,10 @@
import java.time.format.DateTimeFormatter;
/**
- * Trace automatique d'une action de controle effectuee sur une machine virtuelle.
+ * Trace automatique d'une action de contrôle effectuée sur une machine virtuelle.
*
- * Cette entite est separee de {@link VirtualMachine} afin de conserver un
- * historique potentiellement volumineux sans alourdir le modele de la VM.
+ * Cette entité est séparée de {@link VirtualMachine} afin de conserver un
+ * historique potentiellement volumineux sans alourdir le modèle de la VM.
*/
public class AuditEntry {
private static final DateTimeFormatter LOG_TIMESTAMP_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
@@ -20,13 +20,13 @@ public class AuditEntry {
private String labName;
/**
- * Cree une entree d'audit vide.
+ * Crée une entrée d'audit vide.
*/
public AuditEntry() {
}
/**
- * Retourne l'identifiant local de l'entree d'audit.
+ * Retourne l'identifiant local de l'entrée d'audit.
*
* @return identifiant local
*/
@@ -35,7 +35,7 @@ public int getId() {
}
/**
- * Definit l'identifiant local de l'entree d'audit.
+ * Définit l'identifiant local de l'entrée d'audit.
*
* @param id identifiant local
*/
@@ -53,7 +53,7 @@ public LocalDateTime getTimestamp() {
}
/**
- * Definit l'horodatage de l'action.
+ * Définit l'horodatage de l'action.
*
* @param timestamp date et heure de l'action
*/
@@ -62,16 +62,16 @@ public void setTimestamp(LocalDateTime timestamp) {
}
/**
- * Retourne l'identifiant local de la VM concernee.
+ * Retourne l'identifiant local de la VM concernée.
*
- * @return identifiant de la VM, ou {@code null} si aucune VM n'est liee
+ * @return identifiant de la VM, ou {@code null} si aucune VM n'est liée
*/
public Integer getVmId() {
return vmId;
}
/**
- * Definit l'identifiant local de la VM concernee.
+ * Définit l'identifiant local de la VM concernée.
*
* @param vmId identifiant de la VM, ou {@code null}
*/
@@ -80,43 +80,43 @@ public void setVmId(Integer vmId) {
}
/**
- * Retourne l'action auditee.
+ * Retourne l'action auditée.
*
- * @return action auditee
+ * @return action auditée
*/
public String getAction() {
return action;
}
/**
- * Definit l'action auditee.
+ * Définit l'action auditée.
*
- * @param action action auditee
+ * @param action action auditée
*/
public void setAction(String action) {
this.action = action;
}
/**
- * Retourne les details de l'action.
+ * Retourne les détails de l'action.
*
- * @return details de l'action
+ * @return détails de l'action
*/
public String getDetails() {
return details;
}
/**
- * Definit les details de l'action.
+ * Définit les détails de l'action.
*
- * @param details details de l'action
+ * @param details détails de l'action
*/
public void setDetails(String details) {
this.details = details;
}
/**
- * Retourne le nom du laboratoire concerne.
+ * Retourne le nom du laboratoire concerné.
*
* @return nom du laboratoire
*/
@@ -125,7 +125,7 @@ public String getLabName() {
}
/**
- * Definit le nom du laboratoire concerne.
+ * Définit le nom du laboratoire concerné.
*
* @param labName nom du laboratoire
*/
@@ -134,7 +134,7 @@ public void setLabName(String labName) {
}
/**
- * Construit une representation textuelle de l'entree d'audit.
+ * Construit une représentation textuelle de l'entrée d'audit.
*
* @return ligne de log lisible
*/
diff --git a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java
index 76fca92..d2287ad 100644
--- a/src/main/java/tg/cyberlabmanager/model/JournalEntry.java
+++ b/src/main/java/tg/cyberlabmanager/model/JournalEntry.java
@@ -3,9 +3,9 @@
import java.time.LocalDateTime;
/**
- * Note d'analyse horodatee saisie par l'analyste pour une machine virtuelle.
+ * Note d'analyse horodatée saisie par l'analyste pour une machine virtuelle.
*
- * Une entree de journal est rattachee au cycle de vie logique d'une
+ *
Une entrée de journal est rattachée au cycle de vie logique d'une
* {@link VirtualMachine}.
*/
public class JournalEntry {
@@ -14,13 +14,13 @@ public class JournalEntry {
private String content;
/**
- * Cree une entree de journal vide.
+ * Crée une entrée de journal vide.
*/
public JournalEntry() {
}
/**
- * Retourne l'identifiant local de l'entree de journal.
+ * Retourne l'identifiant local de l'entrée de journal.
*
* @return identifiant local
*/
@@ -29,7 +29,7 @@ public int getId() {
}
/**
- * Definit l'identifiant local de l'entree de journal.
+ * Définit l'identifiant local de l'entrée de journal.
*
* @param id identifiant local
*/
@@ -47,7 +47,7 @@ public LocalDateTime getTimestamp() {
}
/**
- * Definit l'horodatage de la note.
+ * Définit l'horodatage de la note.
*
* @param timestamp date et heure de la note
*/
@@ -65,7 +65,7 @@ public String getContent() {
}
/**
- * Definit le contenu de la note.
+ * Définit le contenu de la note.
*
* @param content contenu saisi par l'analyste
*/
diff --git a/src/main/java/tg/cyberlabmanager/model/Lab.java b/src/main/java/tg/cyberlabmanager/model/Lab.java
index 2aac0df..8f82900 100644
--- a/src/main/java/tg/cyberlabmanager/model/Lab.java
+++ b/src/main/java/tg/cyberlabmanager/model/Lab.java
@@ -5,11 +5,11 @@
import java.util.List;
/**
- * Entite racine representant un laboratoire thematique.
+ * Entité racine représentant un laboratoire thématique.
*
* Ses attributs correspondent aux colonnes de la table {@code lab}. Les VM
- * rattachees sont exposees en lecture seule et modifiees via les methodes
- * d'agregation.
+ * rattachées sont exposées en lecture seule et modifiées via les méthodes
+ * d'agrégation.
*/
public class Lab {
private int id;
@@ -19,18 +19,18 @@ public class Lab {
private final List virtualMachines = new ArrayList<>();
/**
- * Cree un laboratoire vide.
+ * Crée un laboratoire vide.
*/
public Lab() {
}
/**
- * Cree un laboratoire avec ses champs principaux.
+ * Crée un laboratoire avec ses champs principaux.
*
* @param id identifiant local du laboratoire
* @param title titre du laboratoire
* @param description description du laboratoire
- * @param category categorie du laboratoire
+ * @param category catégorie du laboratoire
*/
public Lab(int id, String title, String description, String category) {
this.id = id;
@@ -49,7 +49,7 @@ public int getId() {
}
/**
- * Definit l'identifiant local du laboratoire.
+ * Définit l'identifiant local du laboratoire.
*
* @param id identifiant local
*/
@@ -67,7 +67,7 @@ public String getTitle() {
}
/**
- * Definit le titre du laboratoire.
+ * Définit le titre du laboratoire.
*
* @param title titre du laboratoire
*/
@@ -85,7 +85,7 @@ public String getDescription() {
}
/**
- * Definit la description du laboratoire.
+ * Définit la description du laboratoire.
*
* @param description description du laboratoire
*/
@@ -94,25 +94,25 @@ public void setDescription(String description) {
}
/**
- * Retourne la categorie du laboratoire.
+ * Retourne la catégorie du laboratoire.
*
- * @return categorie du laboratoire
+ * @return catégorie du laboratoire
*/
public String getCategory() {
return category;
}
/**
- * Definit la categorie du laboratoire.
+ * Définit la catégorie du laboratoire.
*
- * @param category categorie du laboratoire
+ * @param category catégorie du laboratoire
*/
public void setCategory(String category) {
this.category = category;
}
/**
- * Retourne les machines virtuelles rattachees au laboratoire.
+ * Retourne les machines virtuelles rattachées au laboratoire.
*
* @return liste non modifiable des machines virtuelles
*/
@@ -123,7 +123,7 @@ public List getVirtualMachines() {
/**
* Rattache une machine virtuelle au laboratoire.
*
- * @param virtualMachine machine virtuelle a ajouter
+ * @param virtualMachine machine virtuelle à ajouter
*/
public void addVirtualMachine(VirtualMachine virtualMachine) {
virtualMachines.add(virtualMachine);
@@ -132,7 +132,7 @@ public void addVirtualMachine(VirtualMachine virtualMachine) {
/**
* Retire une machine virtuelle du laboratoire.
*
- * @param virtualMachine machine virtuelle a retirer
+ * @param virtualMachine machine virtuelle à retirer
*/
public void removeVirtualMachine(VirtualMachine virtualMachine) {
virtualMachines.remove(virtualMachine);
diff --git a/src/main/java/tg/cyberlabmanager/model/Snapshot.java b/src/main/java/tg/cyberlabmanager/model/Snapshot.java
index 8b8c396..cf69b33 100644
--- a/src/main/java/tg/cyberlabmanager/model/Snapshot.java
+++ b/src/main/java/tg/cyberlabmanager/model/Snapshot.java
@@ -3,11 +3,11 @@
import java.time.LocalDateTime;
/**
- * Instantane VirtualBox rattache a une machine virtuelle.
+ * Instantané VirtualBox rattaché à une machine virtuelle.
*
- * Le modele conserve l'identite locale en base, l'UUID VirtualBox, les
- * metadonnees d'affichage et l'information indiquant si l'instantane a ete pris
- * pendant que la VM etait en execution.
+ * Le modèle conserve l'identité locale en base, l'UUID VirtualBox, les
+ * métadonnées d'affichage et l'information indiquant si l'instantané a été pris
+ * pendant que la VM était en exécution.
*/
public class Snapshot {
private int id;
@@ -18,7 +18,7 @@ public class Snapshot {
private boolean online;
/**
- * Cree un snapshot vide.
+ * Crée un snapshot vide.
*/
public Snapshot() {
}
@@ -33,7 +33,7 @@ public int getId() {
}
/**
- * Definit l'identifiant local du snapshot.
+ * Définit l'identifiant local du snapshot.
*
* @param id identifiant local
*/
@@ -51,7 +51,7 @@ public String getUuid() {
}
/**
- * Definit l'UUID VirtualBox du snapshot.
+ * Définit l'UUID VirtualBox du snapshot.
*
* @param uuid UUID VirtualBox
*/
@@ -69,7 +69,7 @@ public String getName() {
}
/**
- * Definit le nom du snapshot.
+ * Définit le nom du snapshot.
*
* @param name nom du snapshot
*/
@@ -78,18 +78,18 @@ public void setName(String name) {
}
/**
- * Retourne la date de creation du snapshot.
+ * Retourne la date de création du snapshot.
*
- * @return date de creation
+ * @return date de création
*/
public LocalDateTime getCreatedAt() {
return createdAt;
}
/**
- * Definit la date de creation du snapshot.
+ * Définit la date de création du snapshot.
*
- * @param createdAt date de creation
+ * @param createdAt date de création
*/
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
@@ -105,7 +105,7 @@ public String getDescription() {
}
/**
- * Definit la description du snapshot.
+ * Définit la description du snapshot.
*
* @param description description du snapshot
*/
@@ -114,18 +114,18 @@ public void setDescription(String description) {
}
/**
- * Indique si le snapshot a ete pris VM allumee.
+ * Indique si le snapshot a été pris VM allumée.
*
- * @return {@code true} si le snapshot inclut l'etat en execution
+ * @return {@code true} si le snapshot inclut l'état en exécution
*/
public boolean isOnline() {
return online;
}
/**
- * Definit si le snapshot a ete pris VM allumee.
+ * Définit si le snapshot a été pris VM allumée.
*
- * @param online {@code true} si le snapshot inclut l'etat en execution
+ * @param online {@code true} si le snapshot inclut l'état en exécution
*/
public void setOnline(boolean online) {
this.online = online;
diff --git a/src/main/java/tg/cyberlabmanager/model/VMStatus.java b/src/main/java/tg/cyberlabmanager/model/VMStatus.java
index b014192..3b0130a 100644
--- a/src/main/java/tg/cyberlabmanager/model/VMStatus.java
+++ b/src/main/java/tg/cyberlabmanager/model/VMStatus.java
@@ -1,17 +1,17 @@
package tg.cyberlabmanager.model;
/**
- * Etats stables d'une machine virtuelle tels qu'exposes par l'hyperviseur.
+ * États stables d'une machine virtuelle tels qu'exposés par l'hyperviseur.
*/
public enum VMStatus {
- /** La VM est en cours d'execution. */
+ /** La VM est en cours d'exécution. */
RUNNING,
- /** La VM est eteinte. */
+ /** La VM est éteinte. */
POWERED_OFF,
- /** La VM est dans un etat sauvegarde. */
+ /** La VM est dans un état sauvegardé. */
SAVED,
/** La VM est en pause. */
PAUSED,
- /** Le statut de la VM n'a pas pu etre determine. */
+ /** Le statut de la VM n'a pas pu être déterminé. */
UNKNOWN
}
diff --git a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java
index 18d8f75..e8442cd 100644
--- a/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java
+++ b/src/main/java/tg/cyberlabmanager/model/VirtualMachine.java
@@ -5,11 +5,11 @@
import java.util.List;
/**
- * Machine virtuelle importee depuis VirtualBox.
+ * Machine virtuelle importée depuis VirtualBox.
*
- * L'UUID VirtualBox fournit une reference stable. Les snapshots et notes
- * d'analyse sont encapsules pour eviter l'exposition directe des listes
- * internes.
+ * L'UUID VirtualBox fournit une référence stable. Les snapshots et notes
+ * d'analyse sont encapsulés pour éviter l'exposition directe des listes
+ * internes.
*/
public class VirtualMachine {
private int id;
@@ -21,7 +21,7 @@ public class VirtualMachine {
private final List journalEntries = new ArrayList<>();
/**
- * Cree une machine virtuelle vide.
+ * Crée une machine virtuelle vide.
*/
public VirtualMachine() {
}
@@ -36,7 +36,7 @@ public int getId() {
}
/**
- * Definit l'identifiant local de la VM.
+ * Définit l'identifiant local de la VM.
*
* @param id identifiant local
*/
@@ -54,7 +54,7 @@ public String getName() {
}
/**
- * Definit le nom de la VM.
+ * Définit le nom de la VM.
*
* @param name nom de la VM
*/
@@ -72,7 +72,7 @@ public String getUuid() {
}
/**
- * Definit l'UUID VirtualBox de la VM.
+ * Définit l'UUID VirtualBox de la VM.
*
* @param uuid UUID VirtualBox
*/
@@ -81,7 +81,7 @@ public void setUuid(String uuid) {
}
/**
- * Retourne l'URL de documentation associee a la VM.
+ * Retourne l'URL de documentation associée à la VM.
*
* @return URL de documentation
*/
@@ -90,7 +90,7 @@ public String getDocumentationUrl() {
}
/**
- * Definit l'URL de documentation associee a la VM.
+ * Définit l'URL de documentation associée à la VM.
*
* @param documentationUrl URL de documentation
*/
@@ -108,7 +108,7 @@ public VMStatus getStatus() {
}
/**
- * Definit le statut courant de la VM.
+ * Définit le statut courant de la VM.
*
* @param status statut de la VM
*/
@@ -117,7 +117,7 @@ public void setStatus(VMStatus status) {
}
/**
- * Retourne les snapshots rattaches a la VM.
+ * Retourne les snapshots rattachés à la VM.
*
* @return liste non modifiable des snapshots
*/
@@ -126,16 +126,16 @@ public List getSnapshots() {
}
/**
- * Ajoute un snapshot a la VM.
+ * Ajoute un snapshot à la VM.
*
- * @param snapshot snapshot a ajouter
+ * @param snapshot snapshot à ajouter
*/
public void addSnapshot(Snapshot snapshot) {
snapshots.add(snapshot);
}
/**
- * Retourne les notes d'analyse rattachees a la VM.
+ * Retourne les notes d'analyse rattachées à la VM.
*
* @return liste non modifiable des notes d'analyse
*/
@@ -144,9 +144,9 @@ public List getJournalEntries() {
}
/**
- * Ajoute une note d'analyse a la VM.
+ * Ajoute une note d'analyse à la VM.
*
- * @param journalEntry note d'analyse a ajouter
+ * @param journalEntry note d'analyse à ajouter
*/
public void addJournalEntry(JournalEntry journalEntry) {
journalEntries.add(journalEntry);
From 51ac4bdb50b709442dcc4b3d718d1d3d0023fc06 Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Sat, 30 May 2026 15:00:12 +0000
Subject: [PATCH 08/14] chore: add CI workflow and PR template
---
.github/workflows/ci.yml | 34 ++++++++++++++++++++++
.github/workflows/pull_request_template.md | 25 ++++++++++++++++
2 files changed, 59 insertions(+)
create mode 100644 .github/workflows/ci.yml
create mode 100644 .github/workflows/pull_request_template.md
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..d07d6d8
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,34 @@
+name: CI
+
+on:
+ push:
+ branches: [ "main", "develop", "feature/**", "fix/**" ]
+ pull_request:
+ branches: [ "develop", "main" ]
+
+jobs:
+ build-and-test:
+ name: Build & Test (Java 17)
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Java 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: maven # met le dépôt Maven en cache → builds 2x plus rapides
+
+ - name: Build & run tests
+ run: ./mvnw test --no-transfer-progress
+
+ - name: Upload Surefire reports on failure
+ if: failure() # s'exécute UNIQUEMENT si les tests échouent
+ uses: actions/upload-artifact@v4
+ with:
+ name: surefire-reports
+ path: target/surefire-reports/
+ retention-days: 7
diff --git a/.github/workflows/pull_request_template.md b/.github/workflows/pull_request_template.md
new file mode 100644
index 0000000..b7bbc66
--- /dev/null
+++ b/.github/workflows/pull_request_template.md
@@ -0,0 +1,25 @@
+## Ce que fait ce MR
+
+
+
+## Couche concernée
+
+- [ ] `model` — entités et POJOs
+- [ ] `hypervisor` — IHypervisor / VBoxOrchestrator
+- [ ] `data` — DatabaseManager / schema SQL
+- [ ] `pdf` — PdfExporter
+- [ ] `controller` — LabController
+- [ ] `ui` — vues JavaFX
+- [ ] Config / build (pom.xml, module-info, CI)
+
+## Checklist avant review
+
+- [ ] `./mvnw test` passe en local (0 failure, 0 error)
+- [ ] Javadoc présent sur toutes les classes et méthodes publiques ajoutées
+- [ ] Pas de `System.out.println` de debug oublié
+- [ ] Pas de TODO non intentionnel
+- [ ] Les imports inutilisés ont été supprimés (IntelliJ : Ctrl+Alt+O)
+
+## Pour le reviewer
+
+
From cac0d7bc6dbd80422abd6da80962958fda03668d Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Sat, 30 May 2026 15:16:44 +0000
Subject: [PATCH 09/14] fix: move PR template to correct location
---
.github/{workflows => }/pull_request_template.md | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename .github/{workflows => }/pull_request_template.md (100%)
diff --git a/.github/workflows/pull_request_template.md b/.github/pull_request_template.md
similarity index 100%
rename from .github/workflows/pull_request_template.md
rename to .github/pull_request_template.md
From 4ab5d25f976a50a84cb9c652fcd1c931d1184515 Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Sat, 30 May 2026 15:28:41 +0000
Subject: [PATCH 10/14] Add: Adding CONTRIBUTING to the repo
---
CONTRIBUTING.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 74 insertions(+)
create mode 100644 CONTRIBUTING.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..baae740
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,74 @@
+# Guide de contribution — Cyber Lab Manager
+
+## Branches
+
+| Préfixe | Usage | Exemple |
+|---|---|---|
+| `feature/` | Nouvelle fonctionnalité | `feature/vbox-orchestrator` |
+| `fix/` | Correction de bug | `fix/snapshot-uuid-null` |
+| `chore/` | Config, CI, docs | `chore/github-config` |
+| `test/` | Ajout ou correction de tests seuls | `test/hypervisor-coverage` |
+
+**Règle absolue :** on ne pousse jamais directement sur `develop` ou `main`.
+Toujours passer par un MR depuis une branche `feature/` ou `fix/`.
+
+## Format des commits
+
+On suit [Conventional Commits](https://www.conventionalcommits.org/fr) :
+
+```
+():
+```
+
+| Type | Quand l'utiliser |
+|---|---|
+| `feat` | Ajout d'une fonctionnalité |
+| `fix` | Correction d'un bug |
+| `test` | Ajout ou modification de tests |
+| `docs` | Documentation (Javadoc, Markdown) |
+| `refactor` | Refactoring sans changement de comportement |
+| `chore` | Config build, CI, dépendances |
+
+**Exemples :**
+```
+feat(model): add Snapshot uuid and online fields
+fix(hypervisor): handle VBoxManage non-zero exit code
+test(model): add LabTest unmodifiable list assertion
+docs(hypervisor): complete Javadoc on IHypervisor
+chore: add GitHub Actions CI workflow
+```
+
+## Ouvrir un MR
+
+1. Ta branche est à jour avec `develop` (`git rebase develop` ou `git merge develop`)
+2. `./mvnw test` passe en local — **0 failure, 0 error**
+3. Javadoc présent sur toutes les classes et méthodes publiques ajoutées
+4. Ouvre le MR vers `develop` sur GitHub
+5. Remplis le template de MR (pré-rempli automatiquement)
+6. Assigne au moins un reviewer
+
+## Revue de code
+
+- Le reviewer a **48h** pour donner son retour
+- Un commentaire `[bloquant]` doit être résolu avant le merge
+- Un commentaire `[suggestion]` est optionnel
+- Approuver = confirmer que les tests passent ET que le code est lisible
+
+## Standards de code
+
+- **Java 17**, style standard IntelliJ (Ctrl+Alt+L pour formatter)
+- Javadoc obligatoire sur toutes les classes et méthodes `public`
+- Pas de `System.out.println` → utiliser `java.util.logging.Logger`
+- Imports inutilisés supprimés (Ctrl+Alt+O dans IntelliJ)
+- Pas de TODO non documenté dans un MR
+
+## Responsabilités par package
+
+| Package | Responsable |
+|---|---|
+| `tg.cyberlabmanager.model` | Rôle 3 |
+| `tg.cyberlabmanager.hypervisor` | Rôle 3 |
+| `tg.cyberlabmanager.data` | Rôle 2 |
+| `tg.cyberlabmanager.pdf` | Rôle 2 |
+| `tg.cyberlabmanager.ui` | Rôle 1 |
+| `tg.cyberlabmanager.controller` | Rôle 1 |
From 68d90c3da3a6f6d5e3b70e6e6125c86363bfa31c Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Sat, 30 May 2026 15:30:35 +0000
Subject: [PATCH 11/14] chore: untrack target directory
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index eef09ab..a6268c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,4 @@ build/
### Mac OS ###
.DS_Store
+target/
From 6d2a438f8f940286d1d084222675fe80f4627455 Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Sat, 30 May 2026 15:54:59 +0000
Subject: [PATCH 12/14] docs: add SETUP.md environment guide
---
docs/Markdowns/SETUP.md | 89 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
create mode 100644 docs/Markdowns/SETUP.md
diff --git a/docs/Markdowns/SETUP.md b/docs/Markdowns/SETUP.md
new file mode 100644
index 0000000..738100d
--- /dev/null
+++ b/docs/Markdowns/SETUP.md
@@ -0,0 +1,89 @@
+# SETUP — Environnement de développement
+
+## Prérequis
+
+| Outil | Version minimale | Vérification |
+|---|---|---|
+| JDK | 17 (LTS) | `java -version` |
+| Maven | 3.8+ | `./mvnw -version` |
+| VirtualBox | 7.0+ | Pour les tests d'intégration |
+| Git | 2.x | `git --version` |
+
+> Le projet utilise le Maven Wrapper (`./mvnw`) — pas besoin d'installer Maven globalement.
+
+---
+
+## Configuration IDE — point critique à lire
+
+Le projet compile en **Java 17** même si tu as un JDK plus récent (21, 22…) sur ta machine.
+C'est contrôlé par cette ligne dans `pom.xml` :
+
+```xml
+17
+```
+
+Cette propriété fixe simultanément la syntaxe acceptée, le bytecode cible (class file version 61)
+et les APIs accessibles — le compilateur de ton JDK s'adapte automatiquement.
+
+### IntelliJ IDEA
+
+**File → Project Structure → Project** :
+- SDK : ton JDK installé (17, 21, peu importe)
+- Language level : **17**
+
+**File → Settings → Build → Compiler → Java Compiler** :
+- Target bytecode version : **17**
+
+### Pourquoi c'est important
+
+Si quelqu'un compile avec bytecode Java 21 et pousse du code, les autres obtiendront :
+```
+UnsupportedClassVersionError: Unsupported major.minor version 65.0
+```
+La version 65 = Java 21. La version 61 = Java 17 (notre cible).
+Garder tout le monde sur la même cible évite ce type de conflit au merge.
+
+---
+
+## Lancer le projet
+
+```bash
+# Compiler et lancer tous les tests
+./mvnw test
+
+# Lancer l'application JavaFX
+./mvnw javafx:run
+
+# Générer la Javadoc dans target/reports/apidocs/
+./mvnw javadoc:javadoc
+```
+
+> Si tu utilises un JDK non standard, préfixe avec JAVA_HOME :
+> `JAVA_HOME=/usr/lib/jvm/jdk-21.0.11-oracle-x64 ./mvnw test`
+
+---
+
+## Structure des branches
+
+```
+main ← releases stables uniquement
+develop ← intégration continue (branche de référence)
+feature/x ← développement d'une fonctionnalité
+fix/x ← correction de bug
+chore/x ← config, CI, documentation
+```
+
+Voir [CONTRIBUTING.md](../CONTRIBUTING.md) pour les conventions complètes.
+
+---
+
+## Responsabilités par package
+
+| Package | Rôle | Contenu |
+|---|---|---|
+| `tg.cyberlabmanager.model` | Rôle 3 | Entités métier (Lab, VM, Snapshot…) |
+| `tg.cyberlabmanager.hypervisor` | Rôle 3 | IHypervisor, VBoxOrchestrator |
+| `tg.cyberlabmanager.data` | Rôle 2 | DatabaseManager, SQLite |
+| `tg.cyberlabmanager.pdf` | Rôle 2 | PdfExporter |
+| `tg.cyberlabmanager.ui` | Rôle 1 | Vues JavaFX |
+| `tg.cyberlabmanager.controller` | Rôle 1 | LabController |
From 2e605d2c249f2cda58d0ac525bb1558406e31cbf Mon Sep 17 00:00:00 2001
From: akpmarcelin
Date: Sat, 20 Jun 2026 19:34:29 +0000
Subject: [PATCH 13/14] fix(data,pdf): adapt to Role 3 real model classes
---
.../cyberlabmanager/data/DatabaseManager.java | 270 ++++++++----------
.../tg/cyberlabmanager/pdf/PdfExporter.java | 104 +++----
2 files changed, 162 insertions(+), 212 deletions(-)
diff --git a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
index 56ac366..ee485b0 100644
--- a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
+++ b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
@@ -1,53 +1,58 @@
package tg.cyberlabmanager.data;
-import tg.cyberlabmanager.model.*;
+import tg.cyberlabmanager.model.AppConfig;
+import tg.cyberlabmanager.model.AuditEntry;
+import tg.cyberlabmanager.model.JournalEntry;
+import tg.cyberlabmanager.model.Lab;
+import tg.cyberlabmanager.model.Snapshot;
+import tg.cyberlabmanager.model.VMStatus;
+import tg.cyberlabmanager.model.VirtualMachine;
import java.sql.*;
+import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
- * Gestionnaire de la base de données SQLite de Cyber Lab Manager.
- * Fournit toutes les opérations CRUD pour les labos, VMs, snapshots,
- * journal de bord, audit et configuration.
+ * Service d'accès aux données de l'application.
*
- * Utilisation normale : new DatabaseManager("jdbc:sqlite:/home/user/cyberlab.db")
- * Utilisation en test : new DatabaseManager("jdbc:sqlite::memory:")
+ * Il masque la persistance SQLite derrière des opérations de haut niveau sur
+ * les entités métier.
*/
public class DatabaseManager {
private final String jdbcUrl;
-
- // =========================================================================
+
// Constructeur + initialisation
- // =========================================================================
/**
- * Crée le DatabaseManager et initialise les tables si elles n'existent pas.
+ * Crée un gestionnaire de base de données utilisant le fichier SQLite
+ * par défaut situé dans le répertoire utilisateur.
+ */
+ public DatabaseManager() {
+ this("jdbc:sqlite:" + System.getProperty("user.home") + "/cyberlab.db");
+ }
+
+ /**
+ * Crée un gestionnaire de base de données avec une URL JDBC explicite.
+ * Utile pour les tests (ex: "jdbc:sqlite::memory:").
*
- * @param jdbcUrl URL JDBC SQLite, ex: "jdbc:sqlite:/home/user/cyberlab.db"
- * ou "jdbc:sqlite::memory:" pour les tests
+ * @param jdbcUrl URL JDBC SQLite
*/
public DatabaseManager(String jdbcUrl) {
this.jdbcUrl = jdbcUrl;
initDatabase();
}
- /**
- * Ouvre et retourne une connexion SQLite.
- * Toujours utilisée dans un try-with-resources pour garantir la fermeture.
- */
private Connection getConnection() throws SQLException {
return DriverManager.getConnection(jdbcUrl);
}
/**
* Crée toutes les tables si elles n'existent pas encore.
- * Appelé automatiquement au démarrage.
*/
private void initDatabase() {
String[] tables = {
- // Table des laboratoires
"CREATE TABLE IF NOT EXISTS labo (" +
" id_labo INTEGER PRIMARY KEY AUTOINCREMENT," +
" titre TEXT NOT NULL," +
@@ -55,26 +60,25 @@ private void initDatabase() {
" categorie TEXT" +
");",
- // Table des machines virtuelles
"CREATE TABLE IF NOT EXISTS vm (" +
- " id_vm INTEGER PRIMARY KEY AUTOINCREMENT," +
- " nom_vm TEXT NOT NULL," +
- " uuid TEXT UNIQUE," +
- " id_labo INTEGER REFERENCES labo(id_labo) ON DELETE SET NULL," +
- " lien_doc TEXT," +
- " chemin_vboxmanage TEXT" +
+ " id_vm INTEGER PRIMARY KEY AUTOINCREMENT," +
+ " nom_vm TEXT NOT NULL," +
+ " uuid TEXT UNIQUE," +
+ " id_labo INTEGER REFERENCES labo(id_labo) ON DELETE SET NULL," +
+ " lien_doc TEXT," +
+ " statut TEXT" +
");",
- // Table des snapshots
"CREATE TABLE IF NOT EXISTS snapshot (" +
" id_snap INTEGER PRIMARY KEY AUTOINCREMENT," +
" id_vm INTEGER NOT NULL REFERENCES vm(id_vm) ON DELETE CASCADE," +
+ " uuid TEXT," +
" nom_snap TEXT," +
" date_creation TEXT," +
- " description TEXT" +
+ " description TEXT," +
+ " online INTEGER DEFAULT 0" +
");",
- // Table du journal de bord
"CREATE TABLE IF NOT EXISTS journal_entry (" +
" id_entry INTEGER PRIMARY KEY AUTOINCREMENT," +
" id_vm INTEGER NOT NULL REFERENCES vm(id_vm) ON DELETE CASCADE," +
@@ -82,16 +86,15 @@ private void initDatabase() {
" contenu TEXT NOT NULL" +
");",
- // Table d'audit automatique
"CREATE TABLE IF NOT EXISTS audit_log (" +
" id_log INTEGER PRIMARY KEY AUTOINCREMENT," +
" horodatage TEXT NOT NULL," +
" id_vm INTEGER REFERENCES vm(id_vm) ON DELETE SET NULL," +
" action TEXT NOT NULL," +
- " details TEXT" +
+ " details TEXT," +
+ " lab_name TEXT" +
");",
- // Table de configuration (clé/valeur)
"CREATE TABLE IF NOT EXISTS app_config (" +
" cle TEXT PRIMARY KEY," +
" valeur TEXT" +
@@ -108,41 +111,34 @@ private void initDatabase() {
}
}
- // =========================================================================
// LABORATOIRES
- // =========================================================================
/**
- * Sauvegarde un laboratoire. Fait un INSERT si l'id est 0, un UPDATE sinon.
- * Après INSERT, l'id généré est assigné à l'objet lab.
+ * Enregistre ou met à jour un laboratoire.
*
- * @param lab le laboratoire à sauvegarder
+ * @param lab laboratoire à persister
*/
public void saveLab(Lab lab) {
if (lab.getId() == 0) {
- // INSERT
String sql = "INSERT INTO labo (titre, description, categorie) VALUES (?, ?, ?)";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
- ps.setString(1, lab.getTitre());
+ ps.setString(1, lab.getTitle());
ps.setString(2, lab.getDescription());
- ps.setString(3, lab.getCategorie());
+ ps.setString(3, lab.getCategory());
ps.executeUpdate();
ResultSet keys = ps.getGeneratedKeys();
- if (keys.next()) {
- lab.setId(keys.getInt(1));
- }
+ if (keys.next()) lab.setId(keys.getInt(1));
} catch (SQLException e) {
throw new RuntimeException("Erreur saveLab (INSERT) : " + e.getMessage(), e);
}
} else {
- // UPDATE
String sql = "UPDATE labo SET titre=?, description=?, categorie=? WHERE id_labo=?";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
- ps.setString(1, lab.getTitre());
+ ps.setString(1, lab.getTitle());
ps.setString(2, lab.getDescription());
- ps.setString(3, lab.getCategorie());
+ ps.setString(3, lab.getCategory());
ps.setInt(4, lab.getId());
ps.executeUpdate();
} catch (SQLException e) {
@@ -152,10 +148,10 @@ public void saveLab(Lab lab) {
}
/**
- * Récupère un laboratoire par son identifiant.
+ * Récupère un laboratoire par son identifiant local.
*
- * @param id l'identifiant du laboratoire
- * @return le Lab correspondant, ou null si introuvable
+ * @param id identifiant local du laboratoire
+ * @return laboratoire correspondant, ou null si introuvable
*/
public Lab getLab(int id) {
String sql = "SELECT id_labo, titre, description, categorie FROM labo WHERE id_labo=?";
@@ -173,9 +169,9 @@ public Lab getLab(int id) {
}
/**
- * Retourne tous les laboratoires enregistrés.
+ * Récupère tous les laboratoires.
*
- * @return liste de tous les Lab, vide si aucun
+ * @return liste des laboratoires
*/
public List getAllLabs() {
List labs = new ArrayList<>();
@@ -193,9 +189,9 @@ public List getAllLabs() {
}
/**
- * Supprime un laboratoire. Les VMs associées deviennent orphelines (id_labo → NULL).
+ * Supprime un laboratoire sans supprimer les VM physiques.
*
- * @param lab le laboratoire à supprimer
+ * @param lab laboratoire à supprimer
*/
public void deleteLab(Lab lab) {
String sql = "DELETE FROM labo WHERE id_labo=?";
@@ -208,37 +204,35 @@ public void deleteLab(Lab lab) {
}
}
- /** Construit un objet Lab depuis un ResultSet. */
private Lab mapLab(ResultSet rs) throws SQLException {
Lab lab = new Lab();
lab.setId(rs.getInt("id_labo"));
- lab.setTitre(rs.getString("titre"));
+ lab.setTitle(rs.getString("titre"));
lab.setDescription(rs.getString("description"));
- lab.setCategorie(rs.getString("categorie"));
+ lab.setCategory(rs.getString("categorie"));
return lab;
}
- // =========================================================================
// MACHINES VIRTUELLES
- // =========================================================================
/**
- * Sauvegarde une VM. INSERT si id==0, UPDATE sinon.
- * Si labId est null, la VM est orpheline (non rattachée à un labo).
+ * Enregistre ou met à jour une machine virtuelle.
*
- * @param vm la machine virtuelle à sauvegarder
- * @param labId l'identifiant du labo associé, ou null pour une VM orpheline
+ * @param vm machine virtuelle à persister
+ * @param labId identifiant du laboratoire rattaché, ou {@code null}
*/
public void saveVirtualMachine(VirtualMachine vm, Integer labId) {
+ String statut = vm.getStatus() != null ? vm.getStatus().name() : VMStatus.UNKNOWN.name();
+
if (vm.getId() == 0) {
- String sql = "INSERT INTO vm (nom_vm, uuid, id_labo, lien_doc, chemin_vboxmanage) VALUES (?,?,?,?,?)";
+ String sql = "INSERT INTO vm (nom_vm, uuid, id_labo, lien_doc, statut) VALUES (?,?,?,?,?)";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
- ps.setString(1, vm.getNomVm());
+ ps.setString(1, vm.getName());
ps.setString(2, vm.getUuid());
if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER);
- ps.setString(4, vm.getLienDoc());
- ps.setString(5, vm.getCheminVboxmanage());
+ ps.setString(4, vm.getDocumentationUrl());
+ ps.setString(5, statut);
ps.executeUpdate();
ResultSet keys = ps.getGeneratedKeys();
if (keys.next()) vm.setId(keys.getInt(1));
@@ -246,14 +240,14 @@ public void saveVirtualMachine(VirtualMachine vm, Integer labId) {
throw new RuntimeException("Erreur saveVirtualMachine (INSERT) : " + e.getMessage(), e);
}
} else {
- String sql = "UPDATE vm SET nom_vm=?, uuid=?, id_labo=?, lien_doc=?, chemin_vboxmanage=? WHERE id_vm=?";
+ String sql = "UPDATE vm SET nom_vm=?, uuid=?, id_labo=?, lien_doc=?, statut=? WHERE id_vm=?";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
- ps.setString(1, vm.getNomVm());
+ ps.setString(1, vm.getName());
ps.setString(2, vm.getUuid());
if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER);
- ps.setString(4, vm.getLienDoc());
- ps.setString(5, vm.getCheminVboxmanage());
+ ps.setString(4, vm.getDocumentationUrl());
+ ps.setString(5, statut);
ps.setInt(6, vm.getId());
ps.executeUpdate();
} catch (SQLException e) {
@@ -263,10 +257,10 @@ public void saveVirtualMachine(VirtualMachine vm, Integer labId) {
}
/**
- * Retourne toutes les VMs rattachées à un laboratoire.
+ * Récupère les machines virtuelles rattachées à un laboratoire.
*
- * @param labId l'identifiant du laboratoire
- * @return liste des VMs du labo
+ * @param labId identifiant local du laboratoire
+ * @return liste des machines virtuelles rattachées
*/
public List getVMsForLab(int labId) {
List vms = new ArrayList<>();
@@ -283,9 +277,9 @@ public List getVMsForLab(int labId) {
}
/**
- * Retourne les VMs sans laboratoire associé.
+ * Récupère les machines virtuelles sans laboratoire.
*
- * @return liste des VMs orphelines
+ * @return liste des machines virtuelles orphelines
*/
public List getOrphanVMs() {
List vms = new ArrayList<>();
@@ -301,10 +295,9 @@ public List getOrphanVMs() {
}
/**
- * Détache une VM de son laboratoire (id_labo devient NULL).
- * Ne supprime pas la VM de l'application ni de VirtualBox.
+ * Détache une machine virtuelle de son laboratoire.
*
- * @param vm la VM à détacher
+ * @param vm machine virtuelle à détacher
*/
public void removeVMFromLab(VirtualMachine vm) {
String sql = "UPDATE vm SET id_labo=NULL WHERE id_vm=?";
@@ -317,35 +310,39 @@ public void removeVMFromLab(VirtualMachine vm) {
}
}
- /** Construit un objet VirtualMachine depuis un ResultSet. */
private VirtualMachine mapVM(ResultSet rs) throws SQLException {
VirtualMachine vm = new VirtualMachine();
vm.setId(rs.getInt("id_vm"));
- vm.setNomVm(rs.getString("nom_vm"));
+ vm.setName(rs.getString("nom_vm"));
vm.setUuid(rs.getString("uuid"));
- vm.setLienDoc(rs.getString("lien_doc"));
- vm.setCheminVboxmanage(rs.getString("chemin_vboxmanage"));
+ vm.setDocumentationUrl(rs.getString("lien_doc"));
+ String statut = rs.getString("statut");
+ try {
+ vm.setStatus(statut != null ? VMStatus.valueOf(statut) : VMStatus.UNKNOWN);
+ } catch (IllegalArgumentException e) {
+ vm.setStatus(VMStatus.UNKNOWN);
+ }
return vm;
}
- // =========================================================================
// SNAPSHOTS
- // =========================================================================
/**
- * Enregistre un snapshot associé à une VM.
+ * Enregistre un snapshot pour une machine virtuelle.
*
- * @param snap le snapshot à persister
- * @param vmId l'identifiant de la VM concernée
+ * @param snap snapshot à persister
+ * @param vmId identifiant local de la machine virtuelle
*/
public void saveSnapshot(Snapshot snap, int vmId) {
- String sql = "INSERT INTO snapshot (id_vm, nom_snap, date_creation, description) VALUES (?,?,?,?)";
+ String sql = "INSERT INTO snapshot (id_vm, uuid, nom_snap, date_creation, description, online) VALUES (?,?,?,?,?,?)";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setInt(1, vmId);
- ps.setString(2, snap.getNomSnap());
- ps.setString(3, snap.getDateCreation());
- ps.setString(4, snap.getDescription());
+ ps.setString(2, snap.getUuid());
+ ps.setString(3, snap.getName());
+ ps.setString(4, snap.getCreatedAt() != null ? snap.getCreatedAt().toString() : null);
+ ps.setString(5, snap.getDescription());
+ ps.setInt(6, snap.isOnline() ? 1 : 0);
ps.executeUpdate();
ResultSet keys = ps.getGeneratedKeys();
if (keys.next()) snap.setId(keys.getInt(1));
@@ -355,10 +352,10 @@ public void saveSnapshot(Snapshot snap, int vmId) {
}
/**
- * Retourne tous les snapshots d'une VM, du plus récent au plus ancien.
+ * Récupère les snapshots d'une machine virtuelle.
*
- * @param vmId l'identifiant de la VM
- * @return liste des snapshots
+ * @param vmId identifiant local de la machine virtuelle
+ * @return liste des snapshots rattachés
*/
public List getSnapshotsForVM(int vmId) {
List snaps = new ArrayList<>();
@@ -374,33 +371,33 @@ public List getSnapshotsForVM(int vmId) {
return snaps;
}
- /** Construit un objet Snapshot depuis un ResultSet. */
private Snapshot mapSnapshot(ResultSet rs) throws SQLException {
Snapshot s = new Snapshot();
s.setId(rs.getInt("id_snap"));
- s.setNomSnap(rs.getString("nom_snap"));
- s.setDateCreation(rs.getString("date_creation"));
+ s.setUuid(rs.getString("uuid"));
+ s.setName(rs.getString("nom_snap"));
+ String dateStr = rs.getString("date_creation");
+ s.setCreatedAt(dateStr != null ? LocalDateTime.parse(dateStr) : null);
s.setDescription(rs.getString("description"));
+ s.setOnline(rs.getInt("online") == 1);
return s;
}
- // =========================================================================
// JOURNAL DE BORD
- // =========================================================================
/**
- * Ajoute une entrée dans le journal de bord d'une VM.
+ * Ajoute une note d'analyse pour une machine virtuelle.
*
- * @param entry l'entrée de journal à persister
- * @param vmId l'identifiant de la VM concernée
+ * @param entry note d'analyse à persister
+ * @param vmId identifiant local de la machine virtuelle
*/
public void addJournalEntry(JournalEntry entry, int vmId) {
String sql = "INSERT INTO journal_entry (id_vm, horodatage, contenu) VALUES (?,?,?)";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setInt(1, vmId);
- ps.setString(2, entry.getHorodatage());
- ps.setString(3, entry.getContenu());
+ ps.setString(2, entry.getTimestamp() != null ? entry.getTimestamp().toString() : LocalDateTime.now().toString());
+ ps.setString(3, entry.getContent());
ps.executeUpdate();
ResultSet keys = ps.getGeneratedKeys();
if (keys.next()) entry.setId(keys.getInt(1));
@@ -410,10 +407,10 @@ public void addJournalEntry(JournalEntry entry, int vmId) {
}
/**
- * Retourne toutes les entrées du journal d'une VM, de la plus récente à la plus ancienne.
+ * Récupère les notes d'analyse d'une machine virtuelle.
*
- * @param vmId l'identifiant de la VM
- * @return liste des entrées de journal
+ * @param vmId identifiant local de la machine virtuelle
+ * @return liste des notes d'analyse
*/
public List getJournalEntriesForVM(int vmId) {
List entries = new ArrayList<>();
@@ -429,32 +426,31 @@ public List getJournalEntriesForVM(int vmId) {
return entries;
}
- /** Construit un objet JournalEntry depuis un ResultSet. */
private JournalEntry mapJournalEntry(ResultSet rs) throws SQLException {
JournalEntry e = new JournalEntry();
e.setId(rs.getInt("id_entry"));
- e.setHorodatage(rs.getString("horodatage"));
- e.setContenu(rs.getString("contenu"));
+ String dateStr = rs.getString("horodatage");
+ e.setTimestamp(dateStr != null ? LocalDateTime.parse(dateStr) : null);
+ e.setContent(rs.getString("contenu"));
return e;
}
- // =========================================================================
// AUDIT
- // =========================================================================
/**
- * Enregistre une entrée d'audit (appelé automatiquement après chaque action sur une VM).
+ * Ajoute une entrée d'audit.
*
- * @param entry l'entrée d'audit à persister
+ * @param entry entrée d'audit à persister
*/
public void addAuditEntry(AuditEntry entry) {
- String sql = "INSERT INTO audit_log (horodatage, id_vm, action, details) VALUES (?,?,?,?)";
+ String sql = "INSERT INTO audit_log (horodatage, id_vm, action, details, lab_name) VALUES (?,?,?,?,?)";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
- ps.setString(1, entry.getHorodatage());
- if (entry.getVmId() > 0) ps.setInt(2, entry.getVmId()); else ps.setNull(2, Types.INTEGER);
+ ps.setString(1, entry.getTimestamp() != null ? entry.getTimestamp().toString() : LocalDateTime.now().toString());
+ if (entry.getVmId() != null) ps.setInt(2, entry.getVmId()); else ps.setNull(2, Types.INTEGER);
ps.setString(3, entry.getAction());
ps.setString(4, entry.getDetails());
+ ps.setString(5, entry.getLabName());
ps.executeUpdate();
ResultSet keys = ps.getGeneratedKeys();
if (keys.next()) entry.setId(keys.getInt(1));
@@ -464,10 +460,10 @@ public void addAuditEntry(AuditEntry entry) {
}
/**
- * Retourne l'historique d'audit d'une VM.
+ * Récupère les entrées d'audit d'une machine virtuelle.
*
- * @param vmId l'identifiant de la VM
- * @return liste des entrées d'audit, de la plus récente à la plus ancienne
+ * @param vmId identifiant local de la machine virtuelle
+ * @return liste des entrées d'audit
*/
public List getAuditLogsForVM(int vmId) {
List logs = new ArrayList<>();
@@ -483,52 +479,44 @@ public List getAuditLogsForVM(int vmId) {
return logs;
}
- /** Construit un objet AuditEntry depuis un ResultSet. */
private AuditEntry mapAuditEntry(ResultSet rs) throws SQLException {
AuditEntry a = new AuditEntry();
a.setId(rs.getInt("id_log"));
- a.setHorodatage(rs.getString("horodatage"));
- a.setVmId(rs.getInt("id_vm"));
+ String dateStr = rs.getString("horodatage");
+ a.setTimestamp(dateStr != null ? LocalDateTime.parse(dateStr) : null);
+ int vmIdValue = rs.getInt("id_vm");
+ a.setVmId(rs.wasNull() ? null : vmIdValue);
a.setAction(rs.getString("action"));
a.setDetails(rs.getString("details"));
+ a.setLabName(rs.getString("lab_name"));
return a;
}
- // =========================================================================
// CONFIGURATION
- // =========================================================================
/**
- * Charge la configuration applicative depuis la base.
- * Retourne une configuration avec des valeurs par défaut si rien n'est enregistré.
+ * Récupère la configuration applicative.
*
- * @return la configuration courante
+ * @return configuration applicative
*/
public AppConfig getConfig() {
AppConfig config = new AppConfig();
config.setVboxManagePath(getConfigValue("vboxManagePath", "VBoxManage"));
- config.setDefaultExportDir(getConfigValue("defaultExportDir",
+ config.setPdfExportDirectory(getConfigValue("pdfExportDirectory",
System.getProperty("user.home")));
return config;
}
/**
- * Sauvegarde la configuration applicative.
+ * Enregistre la configuration applicative.
*
- * @param config la configuration à persister
+ * @param config configuration à persister
*/
public void saveConfig(AppConfig config) {
upsertConfig("vboxManagePath", config.getVboxManagePath());
- upsertConfig("defaultExportDir", config.getDefaultExportDir());
+ upsertConfig("pdfExportDirectory", config.getPdfExportDirectory());
}
- /**
- * Lit une valeur de configuration par sa clé.
- *
- * @param key la clé de configuration
- * @param defaultValue valeur retournée si la clé est absente
- * @return la valeur stockée ou la valeur par défaut
- */
private String getConfigValue(String key, String defaultValue) {
String sql = "SELECT valeur FROM app_config WHERE cle=?";
try (Connection conn = getConnection();
@@ -542,12 +530,6 @@ private String getConfigValue(String key, String defaultValue) {
return defaultValue;
}
- /**
- * Insère ou met à jour une valeur de configuration (UPSERT).
- *
- * @param key la clé
- * @param value la valeur
- */
private void upsertConfig(String key, String value) {
String sql = "INSERT OR REPLACE INTO app_config (cle, valeur) VALUES (?,?)";
try (Connection conn = getConnection();
diff --git a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java
index 18490ef..7a6679f 100644
--- a/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java
+++ b/src/main/java/tg/cyberlabmanager/pdf/PdfExporter.java
@@ -1,6 +1,9 @@
package tg.cyberlabmanager.pdf;
-import tg.cyberlabmanager.model.*;
+import tg.cyberlabmanager.model.JournalEntry;
+import tg.cyberlabmanager.model.Lab;
+import tg.cyberlabmanager.model.Snapshot;
+import tg.cyberlabmanager.model.VirtualMachine;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
@@ -22,31 +25,25 @@
*/
public class PdfExporter {
- // ── Mise en page ──────────────────────────────────────────────────────────
private static final float MARGIN = 50f;
- private static final float PAGE_HEIGHT = PDRectangle.A4.getHeight(); // ~842
- private static final float PAGE_WIDTH = PDRectangle.A4.getWidth(); // ~595
+ private static final float PAGE_HEIGHT = PDRectangle.A4.getHeight();
+ private static final float PAGE_WIDTH = PDRectangle.A4.getWidth();
private static final float CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
- // ── Tailles de police ─────────────────────────────────────────────────────
private static final float FONT_TITLE = 18f;
private static final float FONT_HEADING = 13f;
private static final float FONT_SUBHEAD = 11f;
private static final float FONT_BODY = 10f;
- // ── Polices PDFBox 3.x ────────────────────────────────────────────────────
private static final PDType1Font FONT_BOLD = new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD);
private static final PDType1Font FONT_REGULAR = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
private static final PDType1Font FONT_OBLIQUE = new PDType1Font(Standard14Fonts.FontName.HELVETICA_OBLIQUE);
- // ── État interne de pagination ─────────────────────────────────────────────
+ private static final DateTimeFormatter DISPLAY_FORMAT = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
+
private PDDocument document;
private PDPageContentStream content;
- private float cursorY; // position verticale courante (descend vers 0)
-
- // =========================================================================
- // Méthode publique principale
- // =========================================================================
+ private float cursorY;
/**
* Génère et sauvegarde le rapport PDF d'un laboratoire.
@@ -65,12 +62,12 @@ public void exportLabToPdf(Lab lab,
String filePath) throws IOException {
document = new PDDocument();
- newPage(); // crée la première page et initialise cursorY
+ newPage();
- // ── En-tête du rapport ────────────────────────────────────────────────
- writeLine("Rapport – " + lab.getTitre(), FONT_BOLD, FONT_TITLE, true);
- writeLine("Catégorie : " + nvl(lab.getCategorie(), "Non définie"), FONT_REGULAR, FONT_BODY, false);
- writeLine("Généré le : " + now(), FONT_REGULAR, FONT_BODY, false);
+ // En-tête
+ writeLine("Rapport – " + nvl(lab.getTitle(), "Sans titre"), FONT_BOLD, FONT_TITLE, true);
+ writeLine("Catégorie : " + nvl(lab.getCategory(), "Non définie"), FONT_REGULAR, FONT_BODY, false);
+ writeLine("Généré le : " + now(), FONT_REGULAR, FONT_BODY, false);
if (lab.getDescription() != null && !lab.getDescription().isBlank()) {
writeLine("Description : " + lab.getDescription(), FONT_OBLIQUE, FONT_BODY, false);
}
@@ -78,7 +75,7 @@ public void exportLabToPdf(Lab lab,
separator();
spacer(8f);
- // ── Section 1 : Machines virtuelles ───────────────────────────────────
+ // Section 1 : Machines virtuelles
writeLine("1. Machines virtuelles (" + vms.size() + ")", FONT_BOLD, FONT_HEADING, false);
spacer(6f);
@@ -87,11 +84,12 @@ public void exportLabToPdf(Lab lab,
} else {
for (VirtualMachine vm : vms) {
checkPageBreak(60f);
- writeLine(" ▸ " + vm.getNomVm(), FONT_BOLD, FONT_SUBHEAD, false);
+ writeLine(" ▸ " + nvl(vm.getName(), "Sans nom"), FONT_BOLD, FONT_SUBHEAD, false);
writeLine(" UUID : " + nvl(vm.getUuid(), "—"), FONT_REGULAR, FONT_BODY, false);
- writeLine(" Statut : " + nvl(vm.getStatusLabel(), "Inconnu"), FONT_REGULAR, FONT_BODY, false);
- if (vm.getLienDoc() != null && !vm.getLienDoc().isBlank()) {
- writeLine(" Doc : " + vm.getLienDoc(), FONT_REGULAR, FONT_BODY, false);
+ writeLine(" Statut : " + (vm.getStatus() != null ? vm.getStatus().name() : "UNKNOWN"),
+ FONT_REGULAR, FONT_BODY, false);
+ if (vm.getDocumentationUrl() != null && !vm.getDocumentationUrl().isBlank()) {
+ writeLine(" Doc : " + vm.getDocumentationUrl(), FONT_REGULAR, FONT_BODY, false);
}
spacer(5f);
}
@@ -101,7 +99,7 @@ public void exportLabToPdf(Lab lab,
separator();
spacer(8f);
- // ── Section 2 : Snapshots ─────────────────────────────────────────────
+ // Section 2 : Snapshots
writeLine("2. Snapshots (" + snapshots.size() + ")", FONT_BOLD, FONT_HEADING, false);
spacer(6f);
@@ -110,8 +108,10 @@ public void exportLabToPdf(Lab lab,
} else {
for (Snapshot snap : snapshots) {
checkPageBreak(40f);
- writeLine(" ▸ " + nvl(snap.getNomSnap(), "Sans nom"), FONT_BOLD, FONT_SUBHEAD, false);
- writeLine(" Date : " + nvl(snap.getDateCreation(), "—"), FONT_REGULAR, FONT_BODY, false);
+ writeLine(" ▸ " + nvl(snap.getName(), "Sans nom"), FONT_BOLD, FONT_SUBHEAD, false);
+ writeLine(" Date : " + formatDate(snap.getCreatedAt()), FONT_REGULAR, FONT_BODY, false);
+ writeLine(" Type : " + (snap.isOnline() ? "À chaud (VM allumée)" : "À froid (VM éteinte)"),
+ FONT_REGULAR, FONT_BODY, false);
if (snap.getDescription() != null && !snap.getDescription().isBlank()) {
writeLine(" Note : " + snap.getDescription(), FONT_OBLIQUE, FONT_BODY, false);
}
@@ -123,7 +123,7 @@ public void exportLabToPdf(Lab lab,
separator();
spacer(8f);
- // ── Section 3 : Journal de bord ───────────────────────────────────────
+ // Section 3 : Journal de bord
writeLine("3. Journal de bord (" + journals.size() + " entrée(s))", FONT_BOLD, FONT_HEADING, false);
spacer(6f);
@@ -132,14 +132,12 @@ public void exportLabToPdf(Lab lab,
} else {
for (JournalEntry entry : journals) {
checkPageBreak(40f);
- writeLine(" [" + nvl(entry.getHorodatage(), "—") + "]", FONT_BOLD, FONT_BODY, false);
- // Le contenu peut être long : on le découpe en lignes
- writeWrappedText(" " + nvl(entry.getContenu(), ""), FONT_REGULAR, FONT_BODY);
+ writeLine(" [" + formatDate(entry.getTimestamp()) + "]", FONT_BOLD, FONT_BODY, false);
+ writeWrappedText(" " + nvl(entry.getContent(), ""), FONT_REGULAR, FONT_BODY);
spacer(5f);
}
}
- // ── Pied de page de la dernière page ──────────────────────────────────
writeFooter();
content.close();
@@ -147,13 +145,10 @@ public void exportLabToPdf(Lab lab,
document.close();
}
- // =========================================================================
+
// Gestion des pages
- // =========================================================================
- /**
- * Crée une nouvelle page A4 et réinitialise le curseur en haut.
- */
+
private void newPage() throws IOException {
if (content != null) {
content.close();
@@ -164,30 +159,14 @@ private void newPage() throws IOException {
cursorY = PAGE_HEIGHT - MARGIN;
}
- /**
- * Si l'espace restant est insuffisant pour écrire {@code needed} points,
- * crée une nouvelle page.
- *
- * @param needed espace vertical nécessaire en points
- */
private void checkPageBreak(float needed) throws IOException {
if (cursorY - needed < MARGIN + 20f) {
newPage();
}
}
- // =========================================================================
// Écriture de contenu
- // =========================================================================
- /**
- * Écrit une ligne de texte simple et fait descendre le curseur.
- *
- * @param text le texte à écrire
- * @param font la police PDFBox
- * @param size la taille en points
- * @param underline true pour ajouter une ligne sous le texte (simulée par un trait)
- */
private void writeLine(String text, PDType1Font font, float size, boolean underline)
throws IOException {
checkPageBreak(size + 6f);
@@ -208,17 +187,8 @@ private void writeLine(String text, PDType1Font font, float size, boolean underl
}
}
- /**
- * Écrit un texte long en le découpant sur plusieurs lignes si nécessaire.
- *
- * @param text le texte (potentiellement long)
- * @param font la police
- * @param size la taille
- */
private void writeWrappedText(String text, PDType1Font font, float size) throws IOException {
- // Largeur max en points, convertie en "unités police"
float maxWidth = CONTENT_WIDTH;
-
String[] words = sanitize(text).split(" ");
StringBuilder line = new StringBuilder();
@@ -237,12 +207,10 @@ private void writeWrappedText(String text, PDType1Font font, float size) throws
}
}
- /** Ajoute un espace vertical. */
private void spacer(float points) {
cursorY -= points;
}
- /** Dessine un trait de séparation horizontal. */
private void separator() throws IOException {
content.setLineWidth(0.5f);
content.moveTo(MARGIN, cursorY);
@@ -251,7 +219,6 @@ private void separator() throws IOException {
cursorY -= 2f;
}
- /** Écrit un pied de page discret en bas de la page courante. */
private void writeFooter() throws IOException {
float footerY = MARGIN - 10f;
content.beginText();
@@ -261,18 +228,19 @@ private void writeFooter() throws IOException {
content.endText();
}
- // =========================================================================
+
// Utilitaires
- // =========================================================================
- /** Retourne la valeur ou le fallback si null/blank. */
private String nvl(String value, String fallback) {
return (value != null && !value.isBlank()) ? value : fallback;
}
- /** Date/heure courante formatée pour affichage. */
private String now() {
- return LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"));
+ return LocalDateTime.now().format(DISPLAY_FORMAT);
+ }
+
+ private String formatDate(LocalDateTime dt) {
+ return dt != null ? dt.format(DISPLAY_FORMAT) : "—";
}
/**
From 12d8a64639f0a5973a8166813f673ac003e0f6ca Mon Sep 17 00:00:00 2001
From: akpmarcelin
Date: Sat, 4 Jul 2026 07:21:50 +0000
Subject: [PATCH 14/14] feat(data): implement DatabaseManager with SQLite - 30
tests passing
---
.../cyberlabmanager/data/DatabaseManager.java | 117 ++--
.../data/DatabaseManagerTest.java | 510 ++++++++++++++++++
2 files changed, 571 insertions(+), 56 deletions(-)
create mode 100644 src/test/java/tg/cyberlabmanager/data/DatabaseManagerTest.java
diff --git a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
index ee485b0..ae19aed 100644
--- a/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
+++ b/src/main/java/tg/cyberlabmanager/data/DatabaseManager.java
@@ -16,42 +16,58 @@
/**
* Service d'accès aux données de l'application.
*
- * Il masque la persistance SQLite derrière des opérations de haut niveau sur
- * les entités métier.
+ * Maintient une connexion SQLite unique ouverte pendant toute la durée de
+ * vie de l'instance. Compatible avec SQLite en mémoire (:memory:) pour les
+ * tests.
*/
public class DatabaseManager {
- private final String jdbcUrl;
-
- // Constructeur + initialisation
+ private final Connection connection;
+
+ // =========================================================================
+ // Constructeurs
+ // =========================================================================
/**
- * Crée un gestionnaire de base de données utilisant le fichier SQLite
- * par défaut situé dans le répertoire utilisateur.
+ * Crée un gestionnaire utilisant le fichier cyberlab.db dans le répertoire
+ * utilisateur.
*/
public DatabaseManager() {
this("jdbc:sqlite:" + System.getProperty("user.home") + "/cyberlab.db");
}
/**
- * Crée un gestionnaire de base de données avec une URL JDBC explicite.
- * Utile pour les tests (ex: "jdbc:sqlite::memory:").
+ * Crée un gestionnaire avec une URL JDBC explicite.
+ * Utiliser "jdbc:sqlite::memory:" pour les tests unitaires.
*
* @param jdbcUrl URL JDBC SQLite
*/
public DatabaseManager(String jdbcUrl) {
- this.jdbcUrl = jdbcUrl;
+ try {
+ Class.forName("org.sqlite.JDBC");
+ this.connection = DriverManager.getConnection(jdbcUrl);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Driver SQLite introuvable", e);
+ } catch (SQLException e) {
+ throw new RuntimeException("Impossible d'ouvrir la base de données : " + e.getMessage(), e);
+ }
initDatabase();
}
- private Connection getConnection() throws SQLException {
- return DriverManager.getConnection(jdbcUrl);
+ private Connection getConnection() {
+ return this.connection;
}
/**
* Crée toutes les tables si elles n'existent pas encore.
*/
private void initDatabase() {
+ // Activer les clés étrangères SQLite (désactivées par défaut)
+ try (Statement stmt = getConnection().createStatement()) {
+ stmt.execute("PRAGMA foreign_keys = ON");
+ } catch (SQLException e) {
+ throw new RuntimeException("Impossible d'activer les clés étrangères : " + e.getMessage(), e);
+ }
String[] tables = {
"CREATE TABLE IF NOT EXISTS labo (" +
" id_labo INTEGER PRIMARY KEY AUTOINCREMENT," +
@@ -101,8 +117,7 @@ private void initDatabase() {
");"
};
- try (Connection conn = getConnection();
- Statement stmt = conn.createStatement()) {
+ try (Statement stmt = getConnection().createStatement()) {
for (String sql : tables) {
stmt.execute(sql);
}
@@ -111,7 +126,9 @@ private void initDatabase() {
}
}
+ // =========================================================================
// LABORATOIRES
+ // =========================================================================
/**
* Enregistre ou met à jour un laboratoire.
@@ -121,8 +138,7 @@ private void initDatabase() {
public void saveLab(Lab lab) {
if (lab.getId() == 0) {
String sql = "INSERT INTO labo (titre, description, categorie) VALUES (?, ?, ?)";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, lab.getTitle());
ps.setString(2, lab.getDescription());
ps.setString(3, lab.getCategory());
@@ -134,8 +150,7 @@ public void saveLab(Lab lab) {
}
} else {
String sql = "UPDATE labo SET titre=?, description=?, categorie=? WHERE id_labo=?";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setString(1, lab.getTitle());
ps.setString(2, lab.getDescription());
ps.setString(3, lab.getCategory());
@@ -155,13 +170,10 @@ public void saveLab(Lab lab) {
*/
public Lab getLab(int id) {
String sql = "SELECT id_labo, titre, description, categorie FROM labo WHERE id_labo=?";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setInt(1, id);
ResultSet rs = ps.executeQuery();
- if (rs.next()) {
- return mapLab(rs);
- }
+ if (rs.next()) return mapLab(rs);
} catch (SQLException e) {
throw new RuntimeException("Erreur getLab : " + e.getMessage(), e);
}
@@ -176,12 +188,9 @@ public Lab getLab(int id) {
public List getAllLabs() {
List labs = new ArrayList<>();
String sql = "SELECT id_labo, titre, description, categorie FROM labo ORDER BY titre";
- try (Connection conn = getConnection();
- Statement stmt = conn.createStatement();
+ try (Statement stmt = getConnection().createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
- while (rs.next()) {
- labs.add(mapLab(rs));
- }
+ while (rs.next()) labs.add(mapLab(rs));
} catch (SQLException e) {
throw new RuntimeException("Erreur getAllLabs : " + e.getMessage(), e);
}
@@ -195,8 +204,7 @@ public List getAllLabs() {
*/
public void deleteLab(Lab lab) {
String sql = "DELETE FROM labo WHERE id_labo=?";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setInt(1, lab.getId());
ps.executeUpdate();
} catch (SQLException e) {
@@ -213,7 +221,9 @@ private Lab mapLab(ResultSet rs) throws SQLException {
return lab;
}
+ // =========================================================================
// MACHINES VIRTUELLES
+ // =========================================================================
/**
* Enregistre ou met à jour une machine virtuelle.
@@ -226,8 +236,7 @@ public void saveVirtualMachine(VirtualMachine vm, Integer labId) {
if (vm.getId() == 0) {
String sql = "INSERT INTO vm (nom_vm, uuid, id_labo, lien_doc, statut) VALUES (?,?,?,?,?)";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, vm.getName());
ps.setString(2, vm.getUuid());
if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER);
@@ -241,8 +250,7 @@ public void saveVirtualMachine(VirtualMachine vm, Integer labId) {
}
} else {
String sql = "UPDATE vm SET nom_vm=?, uuid=?, id_labo=?, lien_doc=?, statut=? WHERE id_vm=?";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setString(1, vm.getName());
ps.setString(2, vm.getUuid());
if (labId != null) ps.setInt(3, labId); else ps.setNull(3, Types.INTEGER);
@@ -265,8 +273,7 @@ public void saveVirtualMachine(VirtualMachine vm, Integer labId) {
public List getVMsForLab(int labId) {
List vms = new ArrayList<>();
String sql = "SELECT * FROM vm WHERE id_labo=?";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setInt(1, labId);
ResultSet rs = ps.executeQuery();
while (rs.next()) vms.add(mapVM(rs));
@@ -284,8 +291,7 @@ public List getVMsForLab(int labId) {
public List getOrphanVMs() {
List vms = new ArrayList<>();
String sql = "SELECT * FROM vm WHERE id_labo IS NULL";
- try (Connection conn = getConnection();
- Statement stmt = conn.createStatement();
+ try (Statement stmt = getConnection().createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) vms.add(mapVM(rs));
} catch (SQLException e) {
@@ -301,8 +307,7 @@ public List getOrphanVMs() {
*/
public void removeVMFromLab(VirtualMachine vm) {
String sql = "UPDATE vm SET id_labo=NULL WHERE id_vm=?";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setInt(1, vm.getId());
ps.executeUpdate();
} catch (SQLException e) {
@@ -325,7 +330,9 @@ private VirtualMachine mapVM(ResultSet rs) throws SQLException {
return vm;
}
+ // =========================================================================
// SNAPSHOTS
+ // =========================================================================
/**
* Enregistre un snapshot pour une machine virtuelle.
@@ -335,8 +342,7 @@ private VirtualMachine mapVM(ResultSet rs) throws SQLException {
*/
public void saveSnapshot(Snapshot snap, int vmId) {
String sql = "INSERT INTO snapshot (id_vm, uuid, nom_snap, date_creation, description, online) VALUES (?,?,?,?,?,?)";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setInt(1, vmId);
ps.setString(2, snap.getUuid());
ps.setString(3, snap.getName());
@@ -360,8 +366,7 @@ public void saveSnapshot(Snapshot snap, int vmId) {
public List getSnapshotsForVM(int vmId) {
List snaps = new ArrayList<>();
String sql = "SELECT * FROM snapshot WHERE id_vm=? ORDER BY date_creation DESC";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setInt(1, vmId);
ResultSet rs = ps.executeQuery();
while (rs.next()) snaps.add(mapSnapshot(rs));
@@ -383,7 +388,9 @@ private Snapshot mapSnapshot(ResultSet rs) throws SQLException {
return s;
}
+ // =========================================================================
// JOURNAL DE BORD
+ // =========================================================================
/**
* Ajoute une note d'analyse pour une machine virtuelle.
@@ -393,8 +400,7 @@ private Snapshot mapSnapshot(ResultSet rs) throws SQLException {
*/
public void addJournalEntry(JournalEntry entry, int vmId) {
String sql = "INSERT INTO journal_entry (id_vm, horodatage, contenu) VALUES (?,?,?)";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setInt(1, vmId);
ps.setString(2, entry.getTimestamp() != null ? entry.getTimestamp().toString() : LocalDateTime.now().toString());
ps.setString(3, entry.getContent());
@@ -415,8 +421,7 @@ public void addJournalEntry(JournalEntry entry, int vmId) {
public List getJournalEntriesForVM(int vmId) {
List entries = new ArrayList<>();
String sql = "SELECT * FROM journal_entry WHERE id_vm=? ORDER BY horodatage DESC";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setInt(1, vmId);
ResultSet rs = ps.executeQuery();
while (rs.next()) entries.add(mapJournalEntry(rs));
@@ -435,7 +440,9 @@ private JournalEntry mapJournalEntry(ResultSet rs) throws SQLException {
return e;
}
+ // =========================================================================
// AUDIT
+ // =========================================================================
/**
* Ajoute une entrée d'audit.
@@ -444,8 +451,7 @@ private JournalEntry mapJournalEntry(ResultSet rs) throws SQLException {
*/
public void addAuditEntry(AuditEntry entry) {
String sql = "INSERT INTO audit_log (horodatage, id_vm, action, details, lab_name) VALUES (?,?,?,?,?)";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, entry.getTimestamp() != null ? entry.getTimestamp().toString() : LocalDateTime.now().toString());
if (entry.getVmId() != null) ps.setInt(2, entry.getVmId()); else ps.setNull(2, Types.INTEGER);
ps.setString(3, entry.getAction());
@@ -468,8 +474,7 @@ public void addAuditEntry(AuditEntry entry) {
public List getAuditLogsForVM(int vmId) {
List logs = new ArrayList<>();
String sql = "SELECT * FROM audit_log WHERE id_vm=? ORDER BY horodatage DESC";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setInt(1, vmId);
ResultSet rs = ps.executeQuery();
while (rs.next()) logs.add(mapAuditEntry(rs));
@@ -492,7 +497,9 @@ private AuditEntry mapAuditEntry(ResultSet rs) throws SQLException {
return a;
}
+ // =========================================================================
// CONFIGURATION
+ // =========================================================================
/**
* Récupère la configuration applicative.
@@ -519,8 +526,7 @@ public void saveConfig(AppConfig config) {
private String getConfigValue(String key, String defaultValue) {
String sql = "SELECT valeur FROM app_config WHERE cle=?";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setString(1, key);
ResultSet rs = ps.executeQuery();
if (rs.next()) return rs.getString("valeur");
@@ -532,8 +538,7 @@ private String getConfigValue(String key, String defaultValue) {
private void upsertConfig(String key, String value) {
String sql = "INSERT OR REPLACE INTO app_config (cle, valeur) VALUES (?,?)";
- try (Connection conn = getConnection();
- PreparedStatement ps = conn.prepareStatement(sql)) {
+ try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
ps.setString(1, key);
ps.setString(2, value);
ps.executeUpdate();
diff --git a/src/test/java/tg/cyberlabmanager/data/DatabaseManagerTest.java b/src/test/java/tg/cyberlabmanager/data/DatabaseManagerTest.java
new file mode 100644
index 0000000..9c34f1d
--- /dev/null
+++ b/src/test/java/tg/cyberlabmanager/data/DatabaseManagerTest.java
@@ -0,0 +1,510 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package tg.cyberlabmanager.data;
+
+/**
+ *
+ * @author agokoli
+ */
+import tg.cyberlabmanager.model.*;
+
+import org.junit.jupiter.api.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * Tests unitaires de DatabaseManager.
+ * Utilise SQLite en mémoire (:memory:) — aucun fichier créé sur le disque.
+ * Chaque test repart d'une base vide grâce au @BeforeEach.
+ */
+class DatabaseManagerTest {
+
+ private DatabaseManager db;
+
+ @BeforeEach
+ void setUp() {
+ // Base en mémoire : rapide, isolée, détruite après chaque test
+ db = new DatabaseManager("jdbc:sqlite::memory:");
+ }
+
+ // =========================================================================
+ // LABORATOIRES
+ // =========================================================================
+
+ @Test
+ @DisplayName("saveLab : un nouveau labo reçoit un id généré")
+ void testSaveLabInsert() {
+ Lab lab = new Lab();
+ lab.setTitle("Labo Malware");
+ lab.setDescription("Analyse de malware");
+ lab.setCategory("malware");
+
+ db.saveLab(lab);
+
+ assertNotEquals(0, lab.getId(), "L'id doit être assigné après l'INSERT");
+ }
+
+ @Test
+ @DisplayName("getLab : retrouve un labo par son id")
+ void testGetLab() {
+ Lab lab = new Lab();
+ lab.setTitle("Labo Pentest");
+ lab.setCategory("pentest");
+ db.saveLab(lab);
+
+ Lab retrieved = db.getLab(lab.getId());
+
+ assertNotNull(retrieved);
+ assertEquals("Labo Pentest", retrieved.getTitle());
+ assertEquals("pentest", retrieved.getCategory());
+ }
+
+ @Test
+ @DisplayName("getLab : retourne null si l'id n'existe pas")
+ void testGetLabNotFound() {
+ Lab result = db.getLab(9999);
+ assertNull(result);
+ }
+
+ @Test
+ @DisplayName("saveLab : met à jour un labo existant")
+ void testSaveLabUpdate() {
+ Lab lab = new Lab();
+ lab.setTitle("Ancien titre");
+ lab.setCategory("reseau");
+ db.saveLab(lab);
+
+ lab.setTitle("Nouveau titre");
+ db.saveLab(lab); // doit faire un UPDATE
+
+ Lab updated = db.getLab(lab.getId());
+ assertEquals("Nouveau titre", updated.getTitle());
+ }
+
+ @Test
+ @DisplayName("getAllLabs : retourne tous les labos")
+ void testGetAllLabs() {
+ Lab lab1 = new Lab(); lab1.setTitle("Labo A"); lab1.setCategory("malware");
+ Lab lab2 = new Lab(); lab2.setTitle("Labo B"); lab2.setCategory("pentest");
+ Lab lab3 = new Lab(); lab3.setTitle("Labo C"); lab3.setCategory("reseau");
+
+ db.saveLab(lab1);
+ db.saveLab(lab2);
+ db.saveLab(lab3);
+
+ List labs = db.getAllLabs();
+ assertEquals(3, labs.size());
+ }
+
+ @Test
+ @DisplayName("getAllLabs : retourne une liste vide si aucun labo")
+ void testGetAllLabsEmpty() {
+ List labs = db.getAllLabs();
+ assertNotNull(labs);
+ assertTrue(labs.isEmpty());
+ }
+
+ @Test
+ @DisplayName("deleteLab : supprime un labo existant")
+ void testDeleteLab() {
+ Lab lab = new Lab();
+ lab.setTitle("Labo à supprimer");
+ db.saveLab(lab);
+
+ db.deleteLab(lab);
+
+ assertNull(db.getLab(lab.getId()));
+ assertEquals(0, db.getAllLabs().size());
+ }
+
+ @Test
+ @DisplayName("deleteLab : les VMs rattachées deviennent orphelines")
+ void testDeleteLabOrphansVMs() {
+ Lab lab = new Lab();
+ lab.setTitle("Labo avec VMs");
+ db.saveLab(lab);
+
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("Kali");
+ vm.setUuid("uuid-kali");
+ db.saveVirtualMachine(vm, lab.getId());
+
+ db.deleteLab(lab); // les VMs doivent passer en orphelines
+
+ List orphans = db.getOrphanVMs();
+ assertEquals(1, orphans.size());
+ assertEquals("Kali", orphans.get(0).getName());
+ }
+
+ // =========================================================================
+ // MACHINES VIRTUELLES
+ // =========================================================================
+
+ @Test
+ @DisplayName("saveVirtualMachine : une nouvelle VM reçoit un id généré")
+ void testSaveVMInsert() {
+ Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab);
+
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("Ubuntu-22");
+ vm.setUuid("uuid-ubuntu");
+ vm.setStatus(VMStatus.POWERED_OFF);
+
+ db.saveVirtualMachine(vm, lab.getId());
+
+ assertNotEquals(0, vm.getId(), "L'id doit être assigné après l'INSERT");
+ }
+
+ @Test
+ @DisplayName("saveVirtualMachine : labId null crée une VM orpheline")
+ void testSaveVMOrpheline() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM-Orpheline");
+ vm.setUuid("uuid-orphan");
+
+ db.saveVirtualMachine(vm, null);
+
+ List orphans = db.getOrphanVMs();
+ assertEquals(1, orphans.size());
+ assertEquals("VM-Orpheline", orphans.get(0).getName());
+ }
+
+ @Test
+ @DisplayName("getVMsForLab : retourne les VMs d'un labo")
+ void testGetVMsForLab() {
+ Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab);
+
+ VirtualMachine vm1 = new VirtualMachine(); vm1.setName("VM1"); vm1.setUuid("uuid-1");
+ VirtualMachine vm2 = new VirtualMachine(); vm2.setName("VM2"); vm2.setUuid("uuid-2");
+
+ db.saveVirtualMachine(vm1, lab.getId());
+ db.saveVirtualMachine(vm2, lab.getId());
+
+ List vms = db.getVMsForLab(lab.getId());
+ assertEquals(2, vms.size());
+ }
+
+ @Test
+ @DisplayName("getVMsForLab : ne retourne pas les VMs d'un autre labo")
+ void testGetVMsForLabIsolation() {
+ Lab lab1 = new Lab(); lab1.setTitle("Labo 1"); db.saveLab(lab1);
+ Lab lab2 = new Lab(); lab2.setTitle("Labo 2"); db.saveLab(lab2);
+
+ VirtualMachine vm = new VirtualMachine(); vm.setName("VM du labo 1"); vm.setUuid("uuid-x");
+ db.saveVirtualMachine(vm, lab1.getId());
+
+ List vmsLab2 = db.getVMsForLab(lab2.getId());
+ assertTrue(vmsLab2.isEmpty());
+ }
+
+ @Test
+ @DisplayName("getOrphanVMs : retourne uniquement les VMs sans labo")
+ void testGetOrphanVMs() {
+ Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab);
+
+ VirtualMachine vmRattachee = new VirtualMachine();
+ vmRattachee.setName("VM rattachée"); vmRattachee.setUuid("uuid-r");
+ db.saveVirtualMachine(vmRattachee, lab.getId());
+
+ VirtualMachine vmOrpheline = new VirtualMachine();
+ vmOrpheline.setName("VM orpheline"); vmOrpheline.setUuid("uuid-o");
+ db.saveVirtualMachine(vmOrpheline, null);
+
+ List orphans = db.getOrphanVMs();
+ assertEquals(1, orphans.size());
+ assertEquals("VM orpheline", orphans.get(0).getName());
+ }
+
+ @Test
+ @DisplayName("removeVMFromLab : détache une VM sans la supprimer")
+ void testRemoveVMFromLab() {
+ Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab);
+
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("Kali"); vm.setUuid("uuid-kali");
+ db.saveVirtualMachine(vm, lab.getId());
+
+ db.removeVMFromLab(vm);
+
+ // La VM ne doit plus être dans le labo
+ assertTrue(db.getVMsForLab(lab.getId()).isEmpty());
+ // Mais elle doit être orpheline
+ assertEquals(1, db.getOrphanVMs().size());
+ }
+
+ @Test
+ @DisplayName("saveVirtualMachine : le statut est bien persisté")
+ void testSaveVMStatus() {
+ Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab);
+
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM Running"); vm.setUuid("uuid-run");
+ vm.setStatus(VMStatus.RUNNING);
+ db.saveVirtualMachine(vm, lab.getId());
+
+ List vms = db.getVMsForLab(lab.getId());
+ assertEquals(VMStatus.RUNNING, vms.get(0).getStatus());
+ }
+
+ @Test
+ @DisplayName("saveVirtualMachine : documentationUrl est bien persistée")
+ void testSaveVMDocUrl() {
+ Lab lab = new Lab(); lab.setTitle("Labo"); db.saveLab(lab);
+
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM Doc"); vm.setUuid("uuid-doc");
+ vm.setDocumentationUrl("https://doc.exemple.com");
+ db.saveVirtualMachine(vm, lab.getId());
+
+ List vms = db.getVMsForLab(lab.getId());
+ assertEquals("https://doc.exemple.com", vms.get(0).getDocumentationUrl());
+ }
+
+ // =========================================================================
+ // SNAPSHOTS
+ // =========================================================================
+
+ @Test
+ @DisplayName("saveSnapshot : un nouveau snapshot reçoit un id généré")
+ void testSaveSnapshot() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM"); vm.setUuid("uuid-snap");
+ db.saveVirtualMachine(vm, null);
+
+ Snapshot snap = new Snapshot();
+ snap.setName("Snapshot initial");
+ snap.setDescription("Etat propre");
+ snap.setCreatedAt(LocalDateTime.of(2026, 6, 1, 10, 0));
+ snap.setOnline(false);
+
+ db.saveSnapshot(snap, vm.getId());
+
+ assertNotEquals(0, snap.getId());
+ }
+
+ @Test
+ @DisplayName("getSnapshotsForVM : retourne les snapshots dans l'ordre décroissant")
+ void testGetSnapshotsForVM() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM"); vm.setUuid("uuid-snaps");
+ db.saveVirtualMachine(vm, null);
+
+ Snapshot s1 = new Snapshot(); s1.setName("Snap 1");
+ s1.setCreatedAt(LocalDateTime.of(2026, 1, 1, 0, 0));
+ Snapshot s2 = new Snapshot(); s2.setName("Snap 2");
+ s2.setCreatedAt(LocalDateTime.of(2026, 6, 1, 0, 0));
+
+ db.saveSnapshot(s1, vm.getId());
+ db.saveSnapshot(s2, vm.getId());
+
+ List snaps = db.getSnapshotsForVM(vm.getId());
+ assertEquals(2, snaps.size());
+ // Le plus récent en premier
+ assertEquals("Snap 2", snaps.get(0).getName());
+ }
+
+ @Test
+ @DisplayName("getSnapshotsForVM : retourne liste vide si aucun snapshot")
+ void testGetSnapshotsEmpty() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM"); vm.setUuid("uuid-no-snap");
+ db.saveVirtualMachine(vm, null);
+
+ assertTrue(db.getSnapshotsForVM(vm.getId()).isEmpty());
+ }
+
+ @Test
+ @DisplayName("saveSnapshot : le champ online est bien persisté")
+ void testSnapshotOnline() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM"); vm.setUuid("uuid-online");
+ db.saveVirtualMachine(vm, null);
+
+ Snapshot snap = new Snapshot();
+ snap.setName("Snap chaud");
+ snap.setCreatedAt(LocalDateTime.now());
+ snap.setOnline(true);
+ db.saveSnapshot(snap, vm.getId());
+
+ List snaps = db.getSnapshotsForVM(vm.getId());
+ assertTrue(snaps.get(0).isOnline());
+ }
+
+ // =========================================================================
+ // JOURNAL DE BORD
+ // =========================================================================
+
+ @Test
+ @DisplayName("addJournalEntry : une nouvelle entrée reçoit un id généré")
+ void testAddJournalEntry() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM"); vm.setUuid("uuid-journal");
+ db.saveVirtualMachine(vm, null);
+
+ JournalEntry entry = new JournalEntry();
+ entry.setTimestamp(LocalDateTime.now());
+ entry.setContent("Analyse en cours — comportement suspect détecté");
+
+ db.addJournalEntry(entry, vm.getId());
+
+ assertNotEquals(0, entry.getId());
+ }
+
+ @Test
+ @DisplayName("getJournalEntriesForVM : retourne les entrées de la plus récente à la plus ancienne")
+ void testGetJournalEntries() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM"); vm.setUuid("uuid-j2");
+ db.saveVirtualMachine(vm, null);
+
+ JournalEntry e1 = new JournalEntry();
+ e1.setTimestamp(LocalDateTime.of(2026, 1, 1, 8, 0));
+ e1.setContent("Première note");
+
+ JournalEntry e2 = new JournalEntry();
+ e2.setTimestamp(LocalDateTime.of(2026, 6, 1, 9, 0));
+ e2.setContent("Note récente");
+
+ db.addJournalEntry(e1, vm.getId());
+ db.addJournalEntry(e2, vm.getId());
+
+ List entries = db.getJournalEntriesForVM(vm.getId());
+ assertEquals(2, entries.size());
+ assertEquals("Note récente", entries.get(0).getContent());
+ }
+
+ @Test
+ @DisplayName("getJournalEntriesForVM : retourne liste vide si aucune note")
+ void testGetJournalEntriesEmpty() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM"); vm.setUuid("uuid-j-empty");
+ db.saveVirtualMachine(vm, null);
+
+ assertTrue(db.getJournalEntriesForVM(vm.getId()).isEmpty());
+ }
+
+ // =========================================================================
+ // AUDIT
+ // =========================================================================
+
+ @Test
+ @DisplayName("addAuditEntry : une entrée d'audit reçoit un id généré")
+ void testAddAuditEntry() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM"); vm.setUuid("uuid-audit");
+ db.saveVirtualMachine(vm, null);
+
+ AuditEntry entry = new AuditEntry();
+ entry.setTimestamp(LocalDateTime.now());
+ entry.setVmId(vm.getId());
+ entry.setAction("START");
+ entry.setDetails("Démarrage manuel");
+
+ db.addAuditEntry(entry);
+
+ assertNotEquals(0, entry.getId());
+ }
+
+ @Test
+ @DisplayName("getAuditLogsForVM : retourne les logs d'une VM")
+ void testGetAuditLogs() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM"); vm.setUuid("uuid-audit2");
+ db.saveVirtualMachine(vm, null);
+
+ AuditEntry e1 = new AuditEntry();
+ e1.setTimestamp(LocalDateTime.now());
+ e1.setVmId(vm.getId());
+ e1.setAction("START");
+
+ AuditEntry e2 = new AuditEntry();
+ e2.setTimestamp(LocalDateTime.now());
+ e2.setVmId(vm.getId());
+ e2.setAction("STOP");
+
+ db.addAuditEntry(e1);
+ db.addAuditEntry(e2);
+
+ List logs = db.getAuditLogsForVM(vm.getId());
+ assertEquals(2, logs.size());
+ }
+
+ @Test
+ @DisplayName("addAuditEntry : vmId null est accepté")
+ void testAddAuditEntryNullVmId() {
+ AuditEntry entry = new AuditEntry();
+ entry.setTimestamp(LocalDateTime.now());
+ entry.setVmId(null); // pas de VM liée
+ entry.setAction("APP_START");
+ entry.setDetails("Démarrage de l'application");
+
+ assertDoesNotThrow(() -> db.addAuditEntry(entry));
+ assertNotEquals(0, entry.getId());
+ }
+
+ @Test
+ @DisplayName("addAuditEntry : labName est bien persisté")
+ void testAuditEntryLabName() {
+ VirtualMachine vm = new VirtualMachine();
+ vm.setName("VM"); vm.setUuid("uuid-labname");
+ db.saveVirtualMachine(vm, null);
+
+ AuditEntry entry = new AuditEntry();
+ entry.setTimestamp(LocalDateTime.now());
+ entry.setVmId(vm.getId());
+ entry.setAction("SNAPSHOT_TAKEN");
+ entry.setLabName("Labo Malware");
+
+ db.addAuditEntry(entry);
+
+ List logs = db.getAuditLogsForVM(vm.getId());
+ assertEquals("Labo Malware", logs.get(0).getLabName());
+ }
+
+ // =========================================================================
+ // CONFIGURATION
+ // =========================================================================
+
+ @Test
+ @DisplayName("getConfig : retourne des valeurs par défaut si rien n'est sauvegardé")
+ void testGetConfigDefaults() {
+ AppConfig config = db.getConfig();
+
+ assertNotNull(config);
+ assertEquals("VBoxManage", config.getVboxManagePath());
+ assertNotNull(config.getPdfExportDirectory());
+ }
+
+ @Test
+ @DisplayName("saveConfig et getConfig : les valeurs sont bien persistées")
+ void testSaveAndGetConfig() {
+ AppConfig config = new AppConfig();
+ config.setVboxManagePath("/usr/bin/VBoxManage");
+ config.setPdfExportDirectory("/home/agokoli/rapports");
+
+ db.saveConfig(config);
+
+ AppConfig loaded = db.getConfig();
+ assertEquals("/usr/bin/VBoxManage", loaded.getVboxManagePath());
+ assertEquals("/home/agokoli/rapports", loaded.getPdfExportDirectory());
+ }
+
+ @Test
+ @DisplayName("saveConfig : une deuxième sauvegarde écrase la première")
+ void testSaveConfigOverwrite() {
+ AppConfig config1 = new AppConfig();
+ config1.setVboxManagePath("/chemin/ancien");
+ db.saveConfig(config1);
+
+ AppConfig config2 = new AppConfig();
+ config2.setVboxManagePath("/chemin/nouveau");
+ db.saveConfig(config2);
+
+ AppConfig loaded = db.getConfig();
+ assertEquals("/chemin/nouveau", loaded.getVboxManagePath());
+ }
+}
\ No newline at end of file