From 2776b50c0627de743482262559798747d362c5a9 Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Fri, 29 May 2026 18:47:55 +0000
Subject: [PATCH 01/27] =?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 c204060251366340a6385c78d12d0eebb1c1d6e9 Mon Sep 17 00:00:00 2001
From: TheSOCAnalyst
Date: Fri, 29 May 2026 19:51:31 +0000
Subject: [PATCH 02/27] 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 03/27] 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 04/27] 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 05/27] 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 06/27] 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 38ac373c115035a6f067dff420262941ac4bc821 Mon Sep 17 00:00:00 2001
From: M4dnolyn
Date: Sat, 30 May 2026 14:49:12 +0000
Subject: [PATCH 07/27] feat(ui): validate final mockup & add data stubs
---
docs/Images/mockup-final.png | Bin 0 -> 147376 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 docs/Images/mockup-final.png
diff --git a/docs/Images/mockup-final.png b/docs/Images/mockup-final.png
new file mode 100644
index 0000000000000000000000000000000000000000..f57ca06abb19fd5e19cdba03609cbd4c7879a28c
GIT binary patch
literal 147376
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