Ton dossier Scripts déborde ? Dans ce tutoriel, tu construis ScriptPro, un gestionnaire de scripts IT en Java qui range, exécute et trace tes scripts PowerShell, CMD, Batch, Bash et Python. Tout le code est expliqué étape par étape, avec le pourquoi de chaque choix, et le projet complet est disponible sur GitHub.

☕ Java 21🖥️ JavaFX 21🗄️ SQLite✅ 57 tests JUnit 5⏱️ 45 min de lecture

Tu as un dossier Scripts qui déborde ? Un .ps1 sur le bureau, trois .bat sur une clé USB, un script Python qu’un collègue t’a envoyé par mail… et le jour où tu en as besoin, impossible de remettre la main dessus. Sans parler de savoir qui l’a lancé, quand, et ce qu’il a affiché.

Dans ce tutoriel, on construit ensemble ScriptPro, un gestionnaire de scripts de bureau qui range, exécute et trace tes scripts PowerShell, CMD, Batch, Bash et Python. On part de zéro et on arrive à une application complète : base de données, exécution en direct, contrôle de sécurité, historique, sauvegardes, thème sombre et animations.

Surtout, on ne se contente pas d’empiler du code : à chaque étape, je t’explique pourquoi on fait ce choix plutôt qu’un autre. C’est ça qui te servira sur tes propres projets.

Gestionnaire de scripts IT en Java avec JavaFX et SQLite : ScriptPro
ScriptPro, le gestionnaire de scripts IT que tu vas construire avec Java 21, JavaFX et SQLite.
🎯
À qui s’adresse ce tutoriel ?

Chaque section indique son niveau :

  • 🟢 Débutant : les notions de base, expliquées depuis zéro.
  • 🟡 Intermédiaire : l’organisation du code et les bonnes pratiques.
  • 🔴 Confirmé : les détails techniques pointus (encodage, processus, concurrence).

Débutant ? Lis les 🟢 en entier et survole le reste : tu y reviendras plus tard. Confirmé ? Va directement aux 🔴, c’est là que se cachent les vrais pièges.

Ce que fait ce gestionnaire de scripts

🟢 Débutant

ScriptPro est un gestionnaire de scripts, un vrai coffre-fort pour tes scripts IT. Concrètement, il permet de :

BesoinCe que fait ScriptPro
RangerBibliothèque avec catégories, tags, favoris, archives et recherche instantanée
ÉcrireÉditeur avec coloration syntaxique, numéros de ligne, rechercher/remplacer
RéutiliserVariables {{IP}}, {{HOSTNAME}}… demandées au moment de l’exécution
ExécuterConsole intégrée, sortie en direct, bouton Arrêter, délai maximal
SécuriserAlerte avant toute commande dangereuse (rm -rf, format, shutdown…)
TracerHistorique complet : sortie, erreurs, code retour, durée, utilisateur
ProtégerVariables sensibles masquées, sauvegarde et restauration de la base

Ce gestionnaire de scripts tourne entièrement en local : pas de serveur, pas de compte, pas d’abonnement. Tes scripts restent sur ton poste.

Tu découvres Java en venant d’un autre langage ? Lis d’abord notre guide Java pour développeur PHP et notre tutoriel sur les collections Java (ArrayList, HashMap, HashSet) : tu seras plus à l’aise pour suivre la construction de ce gestionnaire de scripts.

Les technologies et pourquoi elles

🟢 Débutant

Voici la pile technique du gestionnaire de scripts, avec pour chaque brique l’alternative qu’on a écartée et pourquoi.

CoucheTechnologiePourquoi ce choixAlternative écartée
LangageJava 21Version LTS (supportée longtemps), records, switch modernes, très stableJava 8 : trop ancien, sans les syntaxes modernes
InterfaceJavaFX 21Moderne, stylable en CSS, animations intégréesSwing : vieillissant, stylage pénible
Éditeur de codeRichTextFXColoration syntaxique et numéros de ligne prêts à l’emploiTextArea : une seule couleur pour tout le texte
IcônesIkonli + Font Awesome 5Icônes vectorielles, colorables en CSSImages PNG : floues une fois agrandies, une image par couleur
NotificationsControlsFXNotifications « toast » élégantesÀ recoder soi-même
Base de donnéesSQLiteUn simple fichier, aucune installationMySQL : nécessite un serveur, trop lourd pour une appli de bureau
BuildMavenStandard, déclaratif, très documentéGradle : plus souple mais plus complexe pour débuter
TestsJUnit 5La référence en Java, tests paramétrés—
💡
Pourquoi une application de bureau et pas une application web ?

Pour lancer un script PowerShell sur ton poste, il faut… être sur ton poste. Une application web devrait passer par un agent local, gérer l’authentification, sécuriser l’accès distant. Ici, l’application est l’agent : plus simple et plus sûr.

Prérequis

🟢 Débutant
OutilVersionÀ quoi il sert
Windows10 ou 11ScriptPro cible les administrateurs Windows
JDK21 ou plusCompiler et exécuter le Java
Maven3.9 ou plusTélécharger les librairies et lancer l’application
Un éditeurIntelliJ IDEA, VS Code…Lire et modifier le code
FacultatifGit Bash, PythonPour exécuter des scripts Bash et Python
⚠️
Plusieurs JDK installés ?

Tape java -version. Si tu vois 11 ou 17, Maven compilera avec la mauvaise version. Pointe JAVA_HOME vers ton JDK 21+ :

$env:JAVA_HOME = "C:\Program Files\Java\jdk-21"

Installer et lancer en 5 minutes

🟢 Débutant

1. Récupérer le projet

git clone https://github.com/LM-Code-Be/script-pro.git
cd script-pro
PowerShell

2. Lancer l’application

mvn clean javafx:run
PowerShell

La première fois, Maven télécharge toutes les librairies (une ou deux minutes). Ensuite, la fenêtre s’ouvre.

3. Premier démarrage

Au premier lancement, ScriptPro crée un fichier scriptpro.db vide à côté du projet : pas de script ni d’historique, seulement quelques catégories, des variables usuelles et les paramètres par défaut. Pour démarrer vite, ouvre l’écran Modèles et clique sur « Créer depuis ce modèle ».

4. Lancer les tests

mvn test
PowerShell

L’architecture de l’application

L’arborescence

🟢 Débutant
ScriptPro/
├── pom.xml                         ← configuration Maven (dépendances, plugins)
├── src/main/java/com/scriptpro/
│   ├── app/          ScriptProApplication.java  ← point d'entrée, assemble tout
│   ├── models/       Script, ExecutionRecord…   ← les données
│   ├── database/     DatabaseManager            ← schéma, migrations, sauvegarde
│   ├── repositories/ ScriptRepository…          ← lecture/écriture SQL
│   ├── services/     ScriptRunner, SecurityService… ← la logique métier
│   ├── controllers/  DashboardView, EditorView… ← un écran = une classe
│   ├── ui/           MainWindow, UiFactory, Animations ← composants visuels
│   └── utils/        ConsoleCharsets, DateFormatter…    ← petits outils
├── src/main/resources/
│   ├── styles/app.css              ← tout le design
│   └── images/scriptpro-icon.png
└── src/test/java/                  ← tests JUnit
Texte

Les couches, expliquées simplement

Architecture en couches du gestionnaire de scripts ScriptPro : controllers, services, repositories, database
Les quatre couches de ScriptPro : chacune ne parle qu’à celle du dessous.
🟡 Intermédiaire

L’application est découpée en couches. Chaque couche ne parle qu’à celle juste en dessous :

  ┌──────────────────────────────┐
  │  controllers / ui  (écrans)  │  ← ce que l'utilisateur voit et clique
  └──────────────┬───────────────┘
                 ▼
  ┌──────────────────────────────┐
  │  services  (logique métier)  │  ← « exécuter », « vérifier la sécurité »…
  └──────────────┬───────────────┘
                 ▼
  ┌──────────────────────────────┐
  │  repositories  (accès SQL)   │  ← INSERT, SELECT, UPDATE
  └──────────────┬───────────────┘
                 ▼
  ┌──────────────────────────────┐
  │  database  (SQLite)          │  ← le fichier scriptpro.db
  └──────────────────────────────┘
Texte

Pourquoi s’embêter avec des couches ? Imagine qu’on écrive le SQL directement dans le bouton « Sauvegarder ». Ça marche… jusqu’au jour où trois boutons différents sauvegardent un script : tu as trois copies du même SQL à maintenir. Avec les couches :

  • un bug SQL se corrige à un seul endroit (le dépôt) ;
  • la logique d’exécution (ScriptRunner) se teste sans ouvrir de fenêtre ;
  • on pourrait remplacer JavaFX par une interface web sans toucher aux services.

L’injection de dépendances « à la main »

🟡 Intermédiaire

Toutes les pièces sont créées et branchées au même endroit, dans ScriptProApplication.start() :

DatabaseManager databaseManager = new DatabaseManager();
databaseManager.initialize();

ScriptRepository scriptRepository = new ScriptRepository(databaseManager);
// … les autres dépôts

ScriptService scriptService = new ScriptService(scriptRepository, categoryRepository);
SecurityService securityService = new SecurityService(settingsService::customSecurityPatterns);
executionService = new ExecutionService(historyRepository, variableService, settingsService,
        new ScriptRunner(interpreterLocator));
Java

📄 Fichier : src/main/java/com/scriptpro/app/ScriptProApplication.java

Chaque classe reçoit ce dont elle a besoin dans son constructeur, au lieu de le créer elle-même. C’est ce qu’on appelle l’injection de dépendances.

🤔
Pourquoi pas Spring ?

Spring fait exactement ça automatiquement… mais il ajoute des dizaines de Mo, rallonge le démarrage et cache le câblage derrière des annotations. Pour une quinzaine de classes, 30 lignes de new sont plus lisibles et plus rapides. Et pour les tests, c’est idéal : on passe une fausse dépendance au constructeur, et le tour est joué.

Pourquoi pas de FXML ?

🟡 Intermédiaire

JavaFX permet de décrire les écrans en XML (FXML) avec l’outil Scene Builder. Ici, on construit les écrans en Java. Raisons :

  • les écrans sont très dynamiques (cartes générées depuis la base, badges selon le statut) : en FXML il faudrait de toute façon beaucoup de code Java ;
  • tout est au même endroit, et le compilateur vérifie les noms (une faute de frappe dans un fx:id ne se voit qu’à l’exécution) ;
  • le style reste séparé, dans le CSS, ce qui est le vrai intérêt de la séparation.

Étape 1 : le projet Maven

🟢 Débutant

Maven, c’est le « gestionnaire de projet » : tu déclares les librairies dont tu as besoin dans pom.xml, il les télécharge et sait compiler, tester et lancer l’application.

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>21</maven.compiler.release>
    <javafx.version>21.0.6</javafx.version>
    <richtextfx.version>0.11.4</richtextfx.version>
    <sqlite.version>3.46.1.3</sqlite.version>
    <junit.version>5.11.4</junit.version>
</properties>
XML

📄 Fichier : pom.xml

  • sourceEncoding UTF-8 : sans ça, sur un Windows français, les accents des commentaires et des textes de l’interface peuvent être compilés de travers.
  • maven.compiler.release 21 : on compile pour Java 21, peu importe le JDK installé (tant qu’il est ≥ 21).
  • Les versions en propriétés : un seul endroit à modifier pour mettre à jour une librairie.

Le plugin JavaFX permet de lancer l’application avec mvn javafx:run :

<plugin>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-maven-plugin</artifactId>
    <version>0.0.8</version>
    <configuration>
        <mainClass>com.scriptpro.app.ScriptProApplication</mainClass>
        <options>
            <option>--enable-native-access=javafx.graphics,ALL-UNNAMED</option>
        </options>
    </configuration>
</plugin>
XML
💡
Depuis Java 11, JavaFX n’est plus inclus dans le JDK.

C’est pour ça qu’on l’ajoute comme une dépendance Maven ordinaire : chaque projet choisit sa version de JavaFX.

Étape 2 : la base de données SQLite

C’est quoi, SQLite ?

🟢 Débutant

SQLite est une base de données SQL complète… stockée dans un seul fichier. Pas de serveur à installer ni de mot de passe à configurer : le pilote sqlite-jdbc contient tout le moteur. C’est la base la plus utilisée au monde (ton téléphone en contient des dizaines).

Le schéma

🟢 Débutant
TableContenu
scriptsNom, langage, catégorie, tags, description, contenu, auteur, dates, favori, archivé
execution_historyChaque exécution : script, statut, date, durée, code retour, sortie, erreurs, utilisateur
variablesVariables {{NOM}} : valeur par défaut, description, intégrée ou non, sensible ou non
categoriesCatégories et leur couleur
favoritesScripts favoris
settingsParamètres clé/valeur (thème, délai, dossier de travail…)

Le gestionnaire de base

🟡 Intermédiaire

Toute la base passe par DatabaseManager. Point important : on n’ouvre pas une connexion pour toute la durée de l’application, mais une connexion courte par opération :

public Connection getConnection() throws SQLException {
    Connection connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath);
    try (Statement statement = connection.createStatement()) {
        statement.execute("PRAGMA foreign_keys = ON");
    }
    return connection;
}
Java

📄 Fichier : src/main/java/com/scriptpro/database/DatabaseManager.java

Pourquoi des connexions courtes ? Avec SQLite, ouvrir une connexion coûte presque rien (c’est un fichier local). En échange, aucune connexion ne reste ouverte « par erreur », et on peut remplacer la base à chaud lors d’une restauration (étape 12). Chaque dépôt utilise le motif try-with-resources, qui ferme automatiquement la connexion, même en cas d’erreur :

try (Connection connection = databaseManager.getConnection();
     PreparedStatement statement = connection.prepareStatement(
             "UPDATE scripts SET archived = ?, updated_at = ? WHERE id = ?")) {
    statement.setInt(1, archived ? 1 : 0);
    statement.setString(2, Instant.now().toString());
    statement.setLong(3, id);
    statement.executeUpdate();
}
Java

📄 Fichier : src/main/java/com/scriptpro/repositories/ScriptRepository.java

🛡️
Toujours des PreparedStatement avec des ?

N’écris jamais "... WHERE name = '" + nom + "'". Si le nom contient une apostrophe (L'inventaire), la requête plante ; et si quelqu’un écrit '; DROP TABLE scripts; --, c’est ta base qui part. C’est l’injection SQL. Avec ?, le pilote traite la valeur comme une donnée, jamais comme du code.

🤔
Pourquoi stocker les dates en texte (2026-10-25T13:05:00Z) ?

SQLite n’a pas de vrai type « date ». Le format ISO 8601 en UTC (Instant.toString()) a trois avantages : il est lisible, il se trie correctement par ordre alphabétique, et il ne dépend pas du fuseau horaire du poste. La conversion vers l’heure locale ne se fait qu’à l’affichage.

Les migrations : faire évoluer la base sans perdre de données

🟡 Intermédiaire

Quand on a ajouté les tags et les variables sensibles, des utilisateurs avaient déjà une base sans ces colonnes. Leur demander de tout supprimer ? Hors de question. À chaque démarrage, ScriptPro met donc le schéma à niveau :

private void migrate(Connection connection) throws SQLException {
    addColumnIfMissing(connection, "scripts", "tags", "TEXT NOT NULL DEFAULT ''");
    addColumnIfMissing(connection, "variables", "secret", "INTEGER NOT NULL DEFAULT 0");
}

private void addColumnIfMissing(Connection connection, String table, String column, String definition) throws SQLException {
    try (Statement statement = connection.createStatement();
         ResultSet columns = statement.executeQuery("PRAGMA table_info(" + table + ")")) {
        while (columns.next()) {
            if (column.equalsIgnoreCase(columns.getString("name"))) {
                return; // la colonne existe déjà : rien à faire
            }
        }
    }
    try (Statement statement = connection.createStatement()) {
        statement.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + definition);
    }
}
Java

📄 Fichier : src/main/java/com/scriptpro/database/DatabaseManager.java

Le mot clé, c’est idempotent : on peut exécuter la migration 1 fois ou 100 fois, le résultat est le même. PRAGMA table_info liste les colonnes existantes ; on n’ajoute que ce qui manque. Le DEFAULT remplit automatiquement les anciennes lignes.

🔴
Pour aller plus loin :

sur un gros projet, on utiliserait un outil comme Flyway ou Liquibase, avec des fichiers de migration numérotés (V1__init.sql, V2__tags.sql…). Pour deux colonnes, une méthode maison de 15 lignes suffit largement.

Étape 3 : les modèles et les dépôts

Les modèles

🟢 Débutant

Un modèle représente une donnée de l’application. Script contient le nom, le langage, le contenu, etc. Le langage est une énumération, pas une simple chaîne :

public enum ScriptLanguage {
    POWERSHELL("PowerShell", ".ps1"),
    CMD("CMD", ".cmd"),
    BATCH("Batch", ".bat"),
    BASH("Bash", ".sh"),
    PYTHON("Python", ".py");
    // …
}
Java

📄 Fichier : src/main/java/com/scriptpro/models/ScriptLanguage.java

Pourquoi une enum ? Avec une chaîne, rien n’empêche d’écrire "Powershel". Avec une énumération, le compilateur refuse toute valeur inconnue, et chaque switch sur le langage doit traiter tous les cas : si on ajoute un langage demain, le compilateur signale chaque endroit à compléter.

Les records, pour les petites données

🟡 Intermédiaire

Pour les données simples qui ne changent pas, Java 16+ offre les records :

public record SecurityWarning(String rule, int lineNumber, String excerpt) {
}
Java

En une ligne, tu obtiens le constructeur, les accesseurs (rule(), lineNumber()…), equals, hashCode et toString. Un record est immuable : une fois créé, impossible de le modifier par erreur.

🤔
Alors pourquoi Script n’est pas un record ?

Parce qu’un script est modifié dans l’éditeur (nom, contenu, tags…) et que son id n’est connu qu’après l’insertion en base. Une classe avec des setters est plus naturelle ici.

Les dépôts (Repository pattern)

🟡 Intermédiaire

Un dépôt cache le SQL derrière des méthodes parlantes : findAll(), findById(), save(), delete(). La méthode save choisit seule entre INSERT et UPDATE :

public Script save(Script script) {
    Instant now = Instant.now();
    if (script.getCreatedAt() == null) {
        script.setCreatedAt(now);
    }
    script.setUpdatedAt(now);
    if (script.getId() == null) {
        return insert(script);   // nouveau script : pas encore d'identifiant
    }
    return update(script);
}
Java

📄 Fichier : src/main/java/com/scriptpro/repositories/ScriptRepository.java

Le reste du code n’a jamais à se demander « est-ce que ce script existe déjà ? » : il appelle save.

Étape 4 : exécuter un script depuis Java

C’est le cœur du gestionnaire de scripts. Tout se passe dans ScriptRunner, une classe volontairement indépendante de JavaFX pour pouvoir la tester seule.

Le principe

🟢 Débutant

Pour exécuter un script, on :

  1. écrit son contenu dans un fichier temporaire (scriptpro-123.ps1) ;
  2. lance l’interpréteur (powershell.exe, cmd.exe, bash.exe…) sur ce fichier ;
  3. lit ce qu’il affiche, ligne par ligne, en direct ;
  4. attend la fin (ou le délai maximal), récupère le code retour, supprime le fichier.

ProcessBuilder plutôt que Runtime.exec

🟡 Intermédiaire
scriptFile = Files.createTempFile("scriptpro-", language.getExtension());
Files.write(scriptFile, ScriptFiles.encode(language, content));

ProcessBuilder builder = new ProcessBuilder(ScriptFiles.command(language, interpreter.get(), scriptFile));
builder.directory(workingDirectory.toFile());
ScriptFiles.configureEnvironment(language, builder.environment());
Process started = builder.start();
process = started;
// Aucun clavier n'est relié au script : un Read-Host ou input() reçoit « fin de fichier »
// au lieu de bloquer jusqu'au timeout.
started.getOutputStream().close();
Java

📄 Fichier : src/main/java/com/scriptpro/services/ScriptRunner.java

  • ProcessBuilder et non Runtime.exec("...") : on passe la commande sous forme de liste d’arguments. Avec une seule chaîne, un chemin contenant des espaces (C:\Program Files\...) est mal découpé. ProcessBuilder permet aussi de choisir le dossier de travail et les variables d’environnement.
  • Pourquoi un fichier temporaire et pas le script en argument (powershell -Command "...") ? Les guillemets, les retours à la ligne et la longueur maximale d’une ligne de commande Windows (environ 8 000 caractères) rendraient tout fragile.
  • Fermer l’entrée standard : si un script attend une saisie (Read-Host, input(), pause), personne ne pourra jamais répondre. En fermant l’entrée, le script reçoit « fin de fichier » immédiatement au lieu de rester bloqué.

Une commande différente par langage

🔴 Confirmé

Chaque interpréteur a ses manies. On les regroupe dans une petite classe ScriptFiles :

static List<String> command(ScriptLanguage language, InterpreterLocator.Interpreter interpreter, Path file) {
    List<String> command = new ArrayList<>();
    command.add(interpreter.executable().toString());
    command.addAll(interpreter.extraArgs());
    String path = file.toAbsolutePath().toString();
    switch (language) {
        case POWERSHELL -> {
            command.addAll(List.of("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command"));
            command.add("[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; "
                    + "$OutputEncoding = [System.Text.Encoding]::UTF8; "
                    + "& '" + path.replace("'", "''") + "'; exit $LASTEXITCODE");
        }
        // /d : ignore les scripts AutoRun du registre, qui pourraient polluer la sortie.
        case CMD, BATCH -> command.addAll(List.of("/d", "/c", path));
        case BASH -> command.add(interpreter.wsl() ? toWslPath(file) : path.replace('\\', '/'));
        case PYTHON -> command.add(path);
    }
    return command;
}
Java
OptionPourquoi
-NoProfileNe charge pas le profil PowerShell de l’utilisateur : démarrage plus rapide, résultat identique sur tous les postes
-NonInteractiveInterdit les invites interactives
-ExecutionPolicy BypassSinon, la stratégie par défaut de Windows refuse d’exécuter un .ps1
exit $LASTEXITCODERemonte le vrai code retour du script (un exit 3 donne bien 3)
cmd /dIgnore les commandes « AutoRun » du registre
/mnt/c/...Le bash de WSL ne comprend pas C:\ : on convertit le chemin

Lire la sortie sans bloquer

🔴 Confirmé

Un processus a deux flux de sortie : la sortie standard (stdout) et les erreurs (stderr). Piège classique : si on lit d’abord stdout jusqu’au bout, puis stderr, et que le script écrit beaucoup d’erreurs, le tampon de stderr se remplit, le script se bloque en attendant qu’on le vide… et nous, on attend la fin de stdout. Interblocage.

La solution : un thread de lecture par flux.

OutputBuffer stdout = new OutputBuffer();
OutputBuffer stderr = new OutputBuffer();
Thread outPump = startPump(started.getInputStream(), stdout, false, fallback);
Thread errPump = startPump(started.getErrorStream(), stderr, true, fallback);

boolean finished = started.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS);
Java

Chaque « pompe » lit les octets, les découpe en lignes et transmet chaque ligne à un listener : c’est ce qui permet l’affichage en direct. La sortie conservée est plafonnée à 2 millions de caractères, pour qu’un script qui boucle à l’infini ne remplisse pas la mémoire ni la base.

Arrêter un script… et ses enfants

🔴 Confirmé

Si ton script lance ping -t, puis que tu cliques sur Arrêter, tuer cmd.exe ne suffit pas : sous Windows, ping continue de tourner, orphelin. On tue donc tout l’arbre de processus :

static void killTree(Process process) {
    List<ProcessHandle> descendants = new ArrayList<>();
    try {
        process.descendants().forEach(descendants::add);
    } catch (RuntimeException ignored) {
        // Énumération impossible (droits) : on tue au moins le processus principal.
    }
    process.destroyForcibly();
    descendants.forEach(ProcessHandle::destroyForcibly);
}
Java

On liste les descendants avant de tuer le parent : une fois le parent mort, le lien de parenté est perdu.

💡
Bonne pratique :

le résultat d’une exécution est un record Result(outcome, exitCode, stdout, stderr, message) avec un outcome parmi COMPLETED, TIMEOUT, CANCELLED, NOT_STARTED. Plutôt que de lancer des exceptions, on renvoie un résultat qui décrit tous les cas, et l’appelant fait un switch dessus.

Étape 5 : le casse-tête des accents

C’est quoi, une page de code ?

🟢 Débutant

Un ordinateur ne stocke que des nombres. Une page de code (ou encodage) est la table qui dit « le nombre 233 = é ». Le problème : il en existe plusieurs.

EncodageOù on le trouveé s’écrit
UTF-8Le standard moderne (web, Java, Linux)2 octets : C3 A9
Windows-1252Les fichiers texte « ANSI » de WindowsE9
OEM 850La console Windows en français (cmd)82

Si un programme écrit en 850 et qu’on lit en UTF-8, Opération réussie devient Op�ration r�ussie. C’était le bug de la première version de ScriptPro.

La solution en deux temps

🔴 Confirmé

1. Demander l’UTF-8 à chaque interpréteur, chacun à sa façon :

static byte[] encode(ScriptLanguage language, String content) {
    return switch (language) {
        // Windows PowerShell 5.1 lit un .ps1 sans BOM en ANSI : le BOM garantit l'UTF-8.
        case POWERSHELL -> concat(UTF8_BOM, content.getBytes(StandardCharsets.UTF_8));
        // cmd.exe lit le fichier ligne par ligne dans la page de code courante : on la passe
        // en UTF-8 dès la première ligne. Les fins de ligne CRLF évitent les bugs de GOTO/labels.
        case CMD, BATCH -> ("@chcp 65001 >nul\r\n" + toCrLf(content)).getBytes(StandardCharsets.UTF_8);
        // Bash refuse les retours chariot Windows (« $'\r': command not found »).
        case BASH -> content.replace("\r\n", "\n").getBytes(StandardCharsets.UTF_8);
        case PYTHON -> content.getBytes(StandardCharsets.UTF_8);
    };
}
Java

📄 Fichier : src/main/java/com/scriptpro/services/ScriptRunner.java

Et pour Python, des variables d’environnement : PYTHONIOENCODING=utf-8, PYTHONUTF8=1, et PYTHONUNBUFFERED=1 (sans ce dernier, Python garde sa sortie en mémoire et rien ne s’affiche en direct).

2. Décoder intelligemment, ligne par ligne. Même avec tout ça, certains vieux programmes (ping, ipconfig) écrivent toujours en OEM 850. On tente donc l’UTF-8 strict, et en cas d’échec on bascule sur la page OEM du poste :

public static String decode(byte[] bytes, int length, Charset fallback) {
    try {
        return StandardCharsets.UTF_8.newDecoder()
                .onMalformedInput(CodingErrorAction.REPORT)
                .onUnmappableCharacter(CodingErrorAction.REPORT)
                .decode(ByteBuffer.wrap(bytes, 0, length))
                .toString();
    } catch (CharacterCodingException notUtf8) {
        return new String(bytes, 0, length, fallback);
    }
}
Java

📄 Fichier : src/main/java/com/scriptpro/utils/ConsoleCharsets.java

Pourquoi ça marche ? L’UTF-8 a une structure très reconnaissable : un octet 82 isolé (le é en OEM 850) est invalide en UTF-8. Le décodeur strict (REPORT) le détecte et lève une exception, et on sait alors qu’il faut utiliser l’autre encodage. Comme on décide ligne par ligne, une sortie qui mélange les deux reste lisible.

La page OEM du poste (850 en France, 437 aux États-Unis…) est lue une seule fois dans le registre Windows (HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage, valeur OEMCP), puis gardée en cache.

Étape 6 : trouver les interpréteurs installés

🟡 Intermédiaire

Avant d’exécuter un script Python, encore faut-il savoir où est Python. InterpreterLocator fait la même recherche que le terminal Windows : il parcourt les dossiers du PATH en essayant les extensions de PATHEXT (.exe, .cmd…).

Quelques choix :

  • Bash : Git Bash avant WSL. Git Bash comprend les chemins Windows ; WSL demande de les convertir. On cherche donc d’abord Git Bash à ses emplacements habituels.
  • Python : le lanceur py -3 en priorité. Installé avec Python sur Windows, il choisit la dernière version 3, même si plusieurs sont présentes.
  • Un cache : la recherche n’est faite qu’une fois par langage. Le bouton « Actualiser la détection » des Paramètres le vide (utile après avoir installé Python).
public synchronized Optional<Interpreter> find(ScriptLanguage language) {
    return cache.computeIfAbsent(language, this::detect);
}
Java

📄 Fichier : src/main/java/com/scriptpro/services/InterpreterLocator.java

💡
Pourquoi Optional ?

La méthode peut ne rien trouver. Plutôt que de renvoyer null, qui finit tôt ou tard en NullPointerException, Optional oblige l’appelant à traiter le cas « absent ». L’éditeur affiche alors un message clair (« Python est introuvable. Installez-le depuis python.org en cochant Add python.exe to PATH ») avant de lancer quoi que ce soit.

Étape 7 : variables dynamiques et secrets

Les variables {{NOM}}

🟢 Débutant

Un même script de diagnostic peut viser n’importe quelle machine : on écrit ping {{IP}}, et ScriptPro demande la valeur de IP avant chaque exécution. La détection se fait avec une expression régulière :

private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{\\{([A-Za-z0-9_-]+)}}");

public String applyVariables(String content, Map<String, String> values) {
    Matcher matcher = VARIABLE_PATTERN.matcher(content == null ? "" : content);
    StringBuilder buffer = new StringBuilder();
    while (matcher.find()) {
        String value = values.get(matcher.group(1).toUpperCase(Locale.ROOT));
        matcher.appendReplacement(buffer, Matcher.quoteReplacement(value == null ? matcher.group(0) : value));
    }
    matcher.appendTail(buffer);
    return buffer.toString();
}
Java

📄 Fichier : src/main/java/com/scriptpro/services/VariableService.java

🔴
Le piège de quoteReplacement.

Dans appendReplacement, les caractères $ et \ ont un sens spécial ($1 = « groupe 1 »). Une valeur comme C:\$Temp ferait planter le remplacement ou donnerait n’importe quoi. Matcher.quoteReplacement les neutralise. Un test vérifie exactement ce cas.

Les variables inconnues sont laissées telles quelles ({{INCONNUE}}) plutôt que remplacées par du vide : le problème se voit tout de suite au lieu de produire une commande silencieusement fausse.

Les variables sensibles

🟡 Intermédiaire

Un mot de passe saisi dans {{MOT_DE_PASSE_ADMIN}} ne doit jamais apparaître en clair à l’écran ni dans l’historique. Trois protections :

  1. la saisie se fait dans un PasswordField (des points à la place des caractères) ;
  2. la sortie en direct passe par un filtre de masquage avant d’être affichée ;
  3. la sortie enregistrée en base est masquée aussi.
public static String mask(String text, Collection<String> secrets) {
    if (text == null || text.isEmpty() || secrets.isEmpty()) {
        return text;
    }
    String masked = text;
    List<String> ordered = secrets.stream()
            .filter(secret -> secret != null && !secret.isEmpty())
            .sorted(Comparator.comparingInt(String::length).reversed())
            .toList();
    for (String secret : ordered) {
        masked = masked.replace(secret, VariableEntry.MASK);
    }
    return masked;
}
Java

Pourquoi trier du plus long au plus court ? Avec les secrets abc et abc123, remplacer abc en premier transformerait abc123 en ••••••123 : il resterait 123 en clair. En commençant par le plus long, tout est masqué.

⚠️
Limite honnête :

la valeur par défaut d’une variable est stockée en clair dans la base. Pour un vrai mot de passe, laisse-la vide : il sera demandé à chaque exécution. Un vrai coffre-fort utiliserait le gestionnaire d’identifiants Windows (DPAPI) ; c’est une piste d’amélioration.

Étape 8 : le contrôle de sécurité

🟡 Intermédiaire

Avant chaque exécution, le gestionnaire de scripts analyse le script et demande confirmation s’il trouve une commande dangereuse. La première version cherchait du texte simple (contains("rm -rf")). Problème : rm -fr, rm -r -f ou Remove-Item -Force -Recurse passaient entre les mailles. On utilise donc des expressions régulières, une par règle :

private static final List<Rule> BUILT_IN_RULES = List.of(
        Rule.of("Suppression récursive de fichiers (rm)",
                "\\brm\\s+(?:\\S+\\s+)*?(?:-[a-z]*r[a-z]*|--recursive)\\b"),
        Rule.of("Suppression de dossier avec sous-dossiers (rd /s)",
                "\\b(?:rd|rmdir)\\b[^\\r\\n]*\\s/s\\b"),
        Rule.of("Formatage de volume",
                "\\bformat(?:\\.com)?\\s+[a-z]:|\\bFormat-Volume\\b"),
        Rule.of("Désactivation de l'antivirus Defender",
                "\\bSet-MpPreference\\b[^\\r\\n]*-Disable\\w*\\s+(?:\\$true|1)\\b"),
        Rule.of("Téléchargement exécuté directement (curl | sh)",
                "\\b(?:curl|wget)\\b[^\\r\\n|]*\\|\\s*(?:sudo\\s+)?(?:ba|z)?sh\\b")
        // … 19 règles au total
);
Java

📄 Fichier : src/main/java/com/scriptpro/services/SecurityService.java

Décortiquons la première pour les débutants :

MorceauSignification
\bDébut de mot : rm mais pas farm
rm\s+rm suivi d’au moins un espace
(?:\S+\s+)*?Éventuellement d’autres arguments avant
-[a-z]*r[a-z]*Une option contenant un r : -r, -rf, -fr, -Rf…
|--recursive… ou la forme longue

Les règles sont insensibles à la casse et appliquées ligne par ligne, ce qui permet d’indiquer à l’utilisateur la ligne exacte et la règle déclenchée. Les lignes de commentaire (#, REM, ::) sont ignorées : elles ne s’exécutent pas.

💡
Garde-fou, pas blindage.

Un script malveillant peut toujours masquer une commande (variable, base64…). Ce contrôle protège surtout contre l’erreur humaine : lancer le mauvais script, ou un script récupéré sans l’avoir relu. Les Paramètres permettent d’ajouter tes propres motifs ; chacun est validé à l’enregistrement pour qu’une regex invalide ne casse pas l’analyse.

Étape 9 : threads et interface réactive

La règle d’or de JavaFX

🟢 Débutant

JavaFX dessine l’interface sur un seul thread, le JavaFX Application Thread. Deux règles en découlent :

  1. Ne jamais bloquer ce thread. Si on y lance un script de 3 minutes, la fenêtre gèle 3 minutes (« Ne répond pas »).
  2. Ne modifier l’interface que depuis ce thread. Sinon, plantages aléatoires.

Exécuter en arrière-plan avec Task

🟡 Intermédiaire

ExecutionService lance chaque exécution dans une Task JavaFX, sur un pool de threads :

Task<ExecutionRecord> task = new Task<>() {
    @Override
    protected ExecutionRecord call() {           // ← tourne en arrière-plan
        Instant startedAt = Instant.now();
        ScriptRunner.Result result = execution.run();
        return historyRepository.save(toRecord(script, startedAt, result, secrets));
    }
};
executorService.submit(task);
Java

📄 Fichier : src/main/java/com/scriptpro/services/ExecutionService.java

Côté éditeur, on réagit à la fin de la tâche avec setOnSucceeded, que JavaFX appelle automatiquement sur le bon thread :

task.setOnSucceeded(event -> {
    ExecutionRecord record = task.getValue();
    finishExecution();
    appendToConsole("\n────────────────────────────\n"
            + record.getStatus().getDisplayName()
            + " · Code retour : " + record.getExitCode()
            + " · Durée : " + DateFormatter.duration(record.getDurationMillis()) + "\n");
});
Java
🤔
Pourquoi des threads « démons » ?

Le pool crée ses threads avec setDaemon(true). Un thread démon n’empêche pas la JVM de s’arrêter : si l’utilisateur ferme l’application, elle se ferme vraiment, au lieu de rester en mémoire à attendre un script.

Afficher la sortie en direct sans saturer l’interface

🔴 Confirmé

Les lignes arrivent depuis les threads de lecture. Pour les afficher, il faut passer par Platform.runLater(...). Mais si un script affiche 10 000 lignes par seconde, 10 000 runLater noient le thread JavaFX et l’interface rame. On regroupe donc les lignes :

private final class LiveConsole {
    private final StringBuilder pending = new StringBuilder();
    private boolean flushScheduled;

    void append(String text) {
        synchronized (pending) {
            pending.append(text);
            if (flushScheduled) {
                return;            // un affichage est déjà prévu : il prendra ce texte aussi
            }
            flushScheduled = true;
        }
        Platform.runLater(this::flush);
    }

    void flush() {
        String text;
        synchronized (pending) {
            text = pending.toString();
            pending.setLength(0);
            flushScheduled = false;
        }
        if (!text.isEmpty()) {
            appendToConsole(text);
        }
    }
}
Java

📄 Fichier : src/main/java/com/scriptpro/controllers/EditorView.java

Tant qu’un affichage est en attente, les nouvelles lignes s’accumulent dans le tampon. Résultat : au plus un runLater en attente à la fois, quel que soit le débit du script. Le synchronized protège le tampon, partagé entre le thread de lecture et le thread JavaFX.

Étape 10 : l’interface JavaFX

La fenêtre principale

🟢 Débutant

MainWindow construit la structure fixe avec un BorderPane, qui découpe la fenêtre en cinq zones :

┌──────────────────────────────────────────────┐
│  TOP : titre de la page, recherche, thème    │
├──────────┬───────────────────────────────────┤
│  LEFT    │  CENTER                           │
│  menu    │  l'écran affiché                  │
│          │                                   │
├──────────┴───────────────────────────────────┤
│  BOTTOM : copyright, état de la base         │
└──────────────────────────────────────────────┘
Texte

Seule la zone centrale change quand on navigue.

Un écran = une classe

🟡 Intermédiaire

La première version avait un MainController de 1 200 lignes qui contenait tous les écrans. Illisible, et chaque modification risquait de casser un autre écran. On l’a découpé : DashboardView, LibraryView, EditorView… Chaque écran reçoit un ViewContext qui lui donne accès aux services et aux utilitaires communs (confirmation, erreurs, notifications) :

void setView(String navigationKey, String title, String subtitle, Node content) {
    window.setActiveNavigation(navigationKey);
    ScrollPane scrollPane = new ScrollPane(content);
    scrollPane.getStyleClass().add("content-scroll");
    scrollPane.setFitToWidth(true);
    scrollPane.setFitToHeight(true);
    window.setContent(title, subtitle, scrollPane);
    updateQuickStats();
}
Java

📄 Fichier : src/main/java/com/scriptpro/controllers/ViewContext.java

Et pour passer d’un écran à l’autre (ex. « Ouvrir » depuis le tableau de bord), les écrans passent par une petite interface Navigator au lieu de se connaître directement. Ça évite les dépendances circulaires : l’éditeur n’a pas besoin de connaître la bibliothèque, et inversement.

Une fabrique de composants

🟢 Débutant

Pour que tous les boutons, badges et cartes se ressemblent, on ne les crée jamais « à la main » : on passe par UiFactory.

public static Button button(String text, String iconLiteral, String styleClass) {
    Button button = new Button(text);
    if (iconLiteral != null && !iconLiteral.isBlank()) {
        button.setGraphic(icon(iconLiteral));
    }
    button.getStyleClass().add(styleClass);
    Animations.hoverScale(button, "nav-button".equals(styleClass) ? 1.02 : 1.04);
    return button;
}
Java

📄 Fichier : src/main/java/com/scriptpro/ui/UiFactory.java

L’intérêt : le jour où on a voulu ajouter une animation au survol de tous les boutons, il a suffi d’une ligne ici.

Des badges colorés dans les tableaux

🟡 Intermédiaire

Par défaut, une cellule de TableView affiche du texte. Pour afficher un badge (Succès en vert, Échec en rouge), on fournit une cell factory qui place un composant graphique dans la cellule :

public static <T> TableColumn<T, String> badgeColumn(String title, Function<T, String> mapper,
                                                     Function<T, String> colorClass) {
    TableColumn<T, String> column = column(title, mapper);
    column.setCellFactory(ignored -> new TableCell<>() {
        @Override
        protected void updateItem(String value, boolean empty) {
            super.updateItem(value, empty);
            T row = empty || getTableRow() == null ? null : getTableRow().getItem();
            setText(null);
            setGraphic(row == null ? null : badge(value, colorClass.apply(row)));
        }
    });
    return column;
}
Java
🔴
Pourquoi tester empty ?

Les TableView recyclent leurs cellules : en faisant défiler, une même cellule affiche successivement plusieurs lignes, puis parfois aucune. Si on oublie de vider la cellule quand empty est vrai, on voit d’anciens badges réapparaître sur des lignes vides.

Étape 11 : le design, les couleurs et les animations

Tout le style dans le CSS

🟢 Débutant

JavaFX comprend une variante du CSS (propriétés préfixées par -fx-). Tout le design de ScriptPro est dans src/main/resources/styles/app.css : le code Java se contente de poser des classes ("kpi-card", "badge-green"…).

Pourquoi ? Changer une couleur ne demande pas de recompiler, et un graphiste peut modifier le design sans toucher au Java.

Des couleurs nommées et le thème sombre

🟡 Intermédiaire

Le CSS de JavaFX permet de définir des couleurs nommées sur la racine et de les réutiliser partout :

.app-root {
    -text-strong: #0f172a;
    -text-muted: #64748b;
    -surface: #ffffff;
    -surface-border: #e4e9f5;
}

.app-root.dark {
    -text-strong: #f8fafc;
    -text-muted: #94a3b8;
    -surface: #121a2c;
    -surface-border: #26324a;
}

.card {
    -fx-background-color: -surface;
    -fx-border-color: -surface-border;
}
CSS

📄 Fichier : src/main/resources/styles/app.css

Le thème sombre consiste juste à ajouter la classe dark sur la racine : toutes les cartes changent de couleur d’un coup, sans dupliquer chaque règle.

Une couleur par sens

🟢 Débutant

Les couleurs ne sont pas décoratives, elles portent du sens, et toujours le même partout :

ÉlémentCouleur
Succès / Échec / Avertissement / ArrêtéVert / Rouge / Orange / Gris
PowerShell / CMD / Batch / Bash / PythonBleu / Ardoise / Orange / Vert / Jaune
Chaque section du menuUne couleur d’icône propre (on repère l’écran d’un coup d’œil)
Actions principalesDégradé indigo → violet

Un utilisateur qui voit un badge rouge dans l’historique comprend avant même de lire.

Les animations

🟡 Intermédiaire

Toutes les animations sont regroupées dans la classe Animations. Exemple, l’entrée d’un élément (fondu + léger glissement vers le haut) :

/** Courbe « ease-out » : démarre vite et ralentit, ce qui paraît plus naturel qu'un mouvement linéaire. */
private static final Interpolator EASE_OUT = Interpolator.SPLINE(0.16, 1, 0.3, 1);

public static void enter(Node node, Duration delay) {
    node.setOpacity(0);
    node.setTranslateY(18);
    FadeTransition fade = new FadeTransition(Duration.millis(420), node);
    fade.setToValue(1);
    TranslateTransition slide = new TranslateTransition(Duration.millis(520), node);
    slide.setToY(0);
    slide.setInterpolator(EASE_OUT);
    ParallelTransition transition = new ParallelTransition(fade, slide);
    transition.setDelay(delay);
    transition.play();
}

public static void stagger(List<? extends Node> nodes) {
    for (int index = 0; index < nodes.size(); index++) {
        // Au-delà de 8 éléments, on plafonne le délai pour ne pas faire attendre l'utilisateur.
        enter(nodes.get(index), Duration.millis(60L * Math.min(index, 8)));
    }
}
Java

📄 Fichier : src/main/java/com/scriptpro/ui/Animations.java

AnimationOùPourquoi
Entrée en cascadeChaque changement de pageL’œil suit l’ordre de lecture, la page « se construit »
Compteur qui défileIndicateurs du tableau de bordAttire l’attention sur les chiffres clés
Barres qui se remplissentScripts par langageRend la comparaison visuelle immédiate
Zoom au survol / à l’appuiTous les boutons, cartes KPIRetour visuel : « oui, c’est cliquable »
💡
Les règles d’une bonne animation :
  • courte (moins de 600 ms) : elle guide l’œil, elle ne fait jamais attendre ;
  • ease-out : un mouvement qui ralentit en arrivant paraît naturel, un mouvement linéaire paraît mécanique ;
  • utile : chaque animation répond à une question (« qu’est-ce qui vient d’apparaître ? », « est-ce cliquable ? »).

Le compteur animé, par exemple, s’appuie sur une Timeline qui fait varier une propriété numérique ; un listener recopie la valeur dans le texte :

public static void countUp(Label label, long target) {
    SimpleLongProperty value = new SimpleLongProperty(0);
    value.addListener((observable, oldValue, newValue) -> label.setText(String.valueOf(newValue)));
    label.setText("0");
    new Timeline(new KeyFrame(Duration.millis(900), new KeyValue(value, target, EASE_OUT))).play();
}
Java

C’est le principe des propriétés observables de JavaFX : on anime une valeur, et tout ce qui l’observe se met à jour.

Étape 12 : sauvegarde et restauration

🟡 Intermédiaire

Copier scriptpro.db pendant que l’application l’utilise peut produire un fichier corrompu (copie au milieu d’une écriture). SQLite propose deux outils fiables :

// Sauvegarde : copie cohérente et compacte, même en cours d'utilisation.
statement.execute("VACUUM INTO '" + absoluteTarget.toString().replace("'", "''") + "'");

// Restauration : API « online backup » de SQLite, via une commande du pilote sqlite-jdbc.
statement.executeUpdate("restore from \"" + absoluteSource + "\"");
Java

📄 Fichier : src/main/java/com/scriptpro/database/DatabaseManager.java

Avant de restaurer, trois sécurités :

  1. le fichier est vérifié (c’est bien une base SQLite contenant les tables ScriptPro) ;
  2. la base actuelle est copiée dans sauvegardes/ : une restauration malheureuse se rattrape ;
  3. après restauration, le schéma est remis à niveau (migration), au cas où la sauvegarde viendrait d’une version plus ancienne.
🤔
Ici, on concatène un chemin dans du SQL… et le PreparedStatement alors ?

VACUUM INTO n’accepte pas de paramètre ?. Le chemin vient d’une boîte de dialogue (pas d’un utilisateur distant), et on double les apostrophes pour qu’un chemin comme C:\Sauvegardes d'avril reste valide.

Étape 13 : les tests automatisés

🟢 Débutant

Un test automatisé est un petit programme qui vérifie que ton code fait ce qu’il doit. On les lance avec mvn test. ScriptPro en compte 57.

Tester sans toucher à la vraie base

🟡 Intermédiaire

JUnit 5 fournit @TempDir : un dossier temporaire créé avant chaque test et supprimé après. Chaque test a sa propre base, jamais la tienne :

@TempDir
Path directory;

@Test
void newDatabaseStartsWithoutScriptsOrHistory() {
    DatabaseManager databaseManager = newDatabase("neuve.db");
    assertTrue(new ScriptRepository(databaseManager).findAll(true).isEmpty());
    assertTrue(new ExecutionHistoryRepository(databaseManager).findAll().isEmpty());
    assertFalse(new CategoryRepository(databaseManager).findAllNames().isEmpty());
}
Java

📄 Fichier : src/test/java/com/scriptpro/database/DatabaseManagerTest.java

Les tests paramétrés

🟡 Intermédiaire

Pour vérifier 22 commandes dangereuses, inutile d’écrire 22 tests : un seul test paramétré suffit.

@ParameterizedTest
@ValueSource(strings = {
        "rm -rf /tmp/test",
        "rm -fr /var/log",
        "Remove-Item -Force -Recurse C:\\Temp",
        "rd /s /q C:\\Temp",
        "shutdown /r /t 0",
        // …
})
void detectsDangerousCommands(String command) {
    assertFalse(service.detectWarnings(command).isEmpty(), "Non détecté : " + command);
}
Java

📄 Fichier : src/test/java/com/scriptpro/services/SecurityServiceTest.java

On teste aussi l’inverse : Get-ChildItem -Recurse ou echo Le formatage est terminé ne doivent pas déclencher d’alerte. Un garde-fou qui crie au loup tout le temps finit par être ignoré.

Tester l’exécution réelle

🔴 Confirmé

ScriptRunnerTest lance de vrais scripts PowerShell et CMD et vérifie : les accents, le code retour, le délai maximal, l’arrêt (aucun processus enfant ne doit survivre). C’est possible parce que ScriptRunner ne dépend pas de JavaFX : c’est la récompense de la séparation en couches vue dans l’architecture. Ces tests sont marqués @EnabledOnOs(OS.WINDOWS) pour ne pas échouer sur Linux.

💡
Bonus : les captures d’écran sont générées par le code.

src/test/java/com/scriptpro/tools/DemoScreenshots.java crée une base de démonstration fictive, parcourt chaque écran et en prend une capture avec scene.snapshot(...). Après un changement de design, une commande suffit à refaire toutes les images du README.

Visite guidée du gestionnaire de scripts

Le tableau de bord

Gestionnaire de scripts ScriptPro : tableau de bord avec indicateurs, activité récente et taux de réussite
Le tableau de bord : indicateurs animés, activité récente, scripts par langage et taux de réussite.

La page d’accueil du gestionnaire de scripts répond en un coup d’œil à « où en est-on ? » :

  • une bannière d’accueil avec deux raccourcis (nouveau script, bibliothèque) ;
  • six indicateurs animés : scripts, favoris, exécutions du jour, échecs, catégories, historique ;
  • l’activité récente avec des badges de statut ;
  • la répartition par langage sous forme de barres ;
  • le taux de réussite avec une barre segmentée (succès, échecs, avertissements, arrêts) ;
  • les derniers scripts, ouvrables en un clic.

La bibliothèque

Bibliothèque du gestionnaire de scripts avec filtres par langage, catégorie et tags
La bibliothèque : recherche instantanée, filtres combinables et cycle de vie complet des scripts.

Le catalogue complet de ton gestionnaire de scripts. La recherche cherche dans le nom, le contenu et les tags à chaque frappe. Les filtres se combinent : langage, catégorie, tag, favoris uniquement, date de modification. Les boutons couvrent tout le cycle de vie : créer, modifier, dupliquer, archiver, supprimer, importer un fichier existant, exporter le source ou un PDF.

💡

L’import détecte l’encodage du fichier (UTF-8 avec ou sans BOM, UTF-16, ANSI) : tes vieux .bat gardent leurs accents.

L’éditeur

Éditeur RichTextFX de ScriptPro avec coloration syntaxique et console intégrée
L’éditeur : coloration syntaxique, variables {{…}} et console d’exécution en direct.

À gauche, la liste des scripts ; à droite, l’éditeur RichTextFX avec coloration syntaxique (mots-clés, chaînes, commentaires, et les variables {{…}} en jaune) et la console d’exécution en dessous.

RaccourciAction
Ctrl+SEnregistrer
F5Exécuter
Maj+F5Arrêter
Ctrl+FRechercher

Au clic sur Exécuter, dans l’ordre : enregistrement, vérification de l’interpréteur, contrôle de sécurité, saisie des variables, puis lancement avec la sortie en direct. Les lignes d’erreur sont préfixées par [ERR].

Les exécutions

Liste des exécutions de scripts avec résultat, durée et code retour
Les exécutions : résultat, durée, code retour et détail complet de la sortie.

Toutes les exécutions, avec des badges de langage et de résultat. Sélectionne une ligne pour voir le détail complet (sortie standard, erreurs, code retour, durée) dans le panneau du bas. Relancer rouvre le script dans l’éditeur et l’exécute à nouveau.

Les favoris

Scripts favoris sous forme de cartes avec boutons Ouvrir et Exécuter
Les favoris : tes scripts du quotidien à un clic.

Tes scripts du quotidien, sous forme de cartes avec deux boutons : Ouvrir et Exécuter. Idéal pour le « script de diagnostic » que tu lances dix fois par jour.

Les modèles

Modèles de scripts PowerShell, CMD, Bash et Python prêts à adapter
Les modèles : des scripts prêts à adapter pour démarrer avec une base vide.

Des scripts prêts à adapter (informations système, services, utilisateurs locaux, diagnostic IP, disque, mémoire…). Un clic sur « Créer depuis ce modèle » copie le modèle dans ta bibliothèque et l’ouvre dans l’éditeur. C’est le point de départ idéal avec une base vide.

Les variables

Variables dynamiques du gestionnaire de scripts, dont une variable sensible masquée
Les variables {{NOM}} : intégrées, personnalisées et sensibles.

Les variables {{NOM}} et leurs valeurs par défaut. Les variables intégrées (IP, HOSTNAME, USERNAME, PATH, SERVICE) ne peuvent pas être supprimées ; les personnalisées si. La case Sensible active la saisie masquée et le masquage dans l’historique.

L’historique

Historique filtrable des exécutions avec export PDF
L’historique : filtres par date, script et résultat, export PDF pour les audits.

La même liste que les Exécutions, avec des filtres en plus (date, nom du script, résultat) et un export PDF, pour retrouver « qui a lancé quoi, quand » ou fournir une trace lors d’un audit.

La documentation intégrée

Documentation intégrée de ScriptPro avec sections dépliables
La documentation intégrée : guide et FAQ, sans connexion Internet.

Un guide d’utilisation et une FAQ directement dans l’application, organisés en sections dépliables : pas besoin d’Internet pour comprendre une fonction.

Les paramètres

Paramètres de ScriptPro : préférences, contrôle de sécurité, interpréteurs et sauvegarde
Les paramètres : préférences, motifs de sécurité, interpréteurs détectés et sauvegarde.
  • Préférences : thème, dossier d’export, dossier de travail des scripts, délai maximal, taille de police, notifications.
  • Contrôle de sécurité : tes propres motifs (une expression régulière par ligne).
  • Interpréteurs détectés : où ScriptPro a trouvé PowerShell, CMD, Bash et Python.
  • Sauvegarde et restauration de la base.

Le thème sombre

Tableau de bord du gestionnaire de scripts en thème sombre
Le thème sombre, activé en un clic et mémorisé.

Un clic sur Sombre dans la barre du haut. Le choix est mémorisé.

Dépannage

ProblèmeCause probableSolution
invalid target release: 21Maven utilise un JDK trop ancienPointe JAVA_HOME vers un JDK 21+
mvn n’est pas reconnuMaven n’est pas dans le PATHInstalle Maven ou ajoute son dossier bin au PATH
« Bash est introuvable »Ni Git Bash ni WSL installéInstalle Git for Windows, puis Actualiser la détection dans les Paramètres
« Python est introuvable »Python absent du PATHRéinstalle Python en cochant Add python.exe to PATH
Le script reste bloquéIl attend une saisie ou une fenêtreScriptPro ferme l’entrée standard ; vérifie que le script n’ouvre pas de programme graphique, sinon le délai maximal l’arrête
Accents bizarres dans la sortieProgramme qui écrit dans une page de code exotiqueSignale-le avec la commande concernée : la détection couvre UTF-8 et la page OEM du poste
« Sortie incomplète »Le script a lancé un programme qui reste ouvert (start notepad)Normal : ScriptPro n’attend pas indéfiniment ce programme
Repartir de zéro—Ferme l’application et supprime scriptpro.db : une base vide est recréée au lancement

Améliorer ScriptPro

Tu veux t’entraîner en faisant évoluer ton gestionnaire de scripts ? Voici des idées, de la plus simple à la plus ambitieuse :

  • 🟢 Raccourci « Dupliquer » (Ctrl+D) dans l’éditeur.
  • 🟢 Nouveaux modèles dans TemplateService (nettoyage de disque, état de BitLocker…).
  • 🟡 Statistiques par script : durée moyenne, taux d’échec, graphique d’évolution.
  • 🟡 Planification : lancer un script tous les jours à 8 h (ScheduledExecutorService, ou mieux, le Planificateur de tâches Windows).
  • 🟡 Exécution à distance via Invoke-Command -ComputerName pour PowerShell.
  • 🔴 Coffre-fort de secrets chiffré avec DPAPI ou le Gestionnaire d’identifiants Windows.
  • 🔴 Installateur Windows (.msi) avec jpackage, base déplacée dans %APPDATA%\ScriptPro. Pense à inclure le module jdk.charsets, sinon le repli d’encodage OEM ne fonctionnera pas.
  • 🔴 Synchronisation de la bibliothèque entre plusieurs postes via un dépôt Git.

FAQ

Mes scripts sont-ils envoyés quelque part ?

Non. Tout est stocké dans scriptpro.db, sur ton poste. ScriptPro ne fait aucune connexion réseau.

Est-ce que ScriptPro fonctionne sous Linux ou macOS ?

JavaFX et SQLite oui, et Bash et Python aussi. Mais l’application a été pensée pour Windows (PowerShell, CMD, pages de code) et n’est testée que là.

Pourquoi Java et pas C# / .NET pour une application Windows ?

.NET aurait été un excellent choix aussi. Java est multiplateforme, très répandu dans l’enseignement, et JavaFX offre un stylage CSS confortable. Les concepts de ce tutoriel (couches, processus, encodage, threads) se transposent directement en C#.

Le contrôle de sécurité suffit-il à empêcher un script malveillant ?

Non, et il ne le prétend pas. C’est un garde-fou contre l’erreur humaine. Ne lance jamais un script que tu n’as pas relu.

Puis-je utiliser PowerShell 7 au lieu de Windows PowerShell 5.1 ?

ScriptPro privilégie powershell.exe (présent sur tous les Windows) pour garantir la compatibilité. pwsh est utilisé s’il est le seul disponible. Tu peux changer cet ordre dans InterpreterLocator.detect().

Comment sauvegarder mes scripts ?

Paramètres → Sauvegarder la base…. Tu obtiens un fichier .db à conserver où tu veux (NAS, cloud…).

Puis-je utiliser ScriptPro dans mon entreprise ?

Oui : le projet est sous licence MIT. Tu peux l’utiliser, le modifier et le redistribuer librement, en conservant la mention de copyright.

Conclusion

Envie d’un autre projet complet pour les admins Windows ? Découvre notre moniteur système Windows en PHP (System Dashboard), qui complète parfaitement ce gestionnaire de scripts.

Tu as maintenant un gestionnaire de scripts complet, et surtout tu sais pourquoi elle est construite ainsi. En résumé, tu as vu :

  • ✅ comment organiser une application en couches et injecter les dépendances sans framework ;
  • ✅ comment utiliser SQLite proprement : connexions courtes, PreparedStatement, migrations idempotentes, sauvegarde à chaud ;
  • ✅ comment lancer des processus depuis Java sans interblocage, avec délai maximal et arrêt de toute l’arborescence ;
  • ✅ comment venir à bout des problèmes d’encodage de la console Windows ;
  • ✅ comment garder une interface JavaFX fluide avec Task, Platform.runLater et le regroupement des mises à jour ;
  • ✅ comment construire un design moderne : CSS, couleurs porteuses de sens, thème sombre et animations ;
  • ✅ comment tester tout ça avec JUnit 5.

Clone le projet, lance-le, casse-le, améliore-le. La meilleure façon d’apprendre reste de modifier un code qui fonctionne : commence par une idée 🟢 de la liste ci-dessus, et monte en difficulté.

Comments

No comments yet. Why don’t you start the discussion?

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *