10 Commits

Author SHA1 Message Date
SashegDev 454d3389b6 fix: добавляем shell скрипт launcher.sh для запуска с javafx module-path 2026-05-05 11:36:36 +00:00
SashegDev 012d1635dc fix: исключаем javafx из shade, исправляем antrun 2026-05-05 11:35:33 +00:00
SashegDev 70c4815032 feat: добавляем win/linux профили для сборки с разными javafx 2026-05-05 11:33:30 +00:00
SashegDev a46ef3e834 fix: исправляем синтаксис antrun copy 2026-05-05 11:29:01 +00:00
SashegDev cba8259e59 fix: копируем JavaFX JAR в builds и исключаем из shade 2026-05-05 11:25:44 +00:00
SashegDev 08417efe2f fix: добавляем JavaFX module-path и настройки для GUI запуска 2026-05-05 11:23:59 +00:00
SashegDev 991252130d fix: добавляем Windows classifier для JavaFX и исключаем Linux jar из shade
- добавили <classifier>win</classifier> в javafx-controls и javafx-web
- добавили фильтр в shade plugin для исключения *-linux.jar
2026-05-05 09:42:49 +00:00
SashegDev 6fa97b7fda fix: убираем дублирующую проверку isHeadless из UIWindow
раньше UIWindow сам проверял isHeadless и выбрасывал исключение
теперь доверяем проверке в Main с учётом переменной DISPLAY
2026-05-05 09:37:01 +00:00
SashegDev 83abc600f3 fix: добавляем проверку DISPLAY для запуска UI на сервере
раньше isHeadless() определял что нет дисплея даже когда он есть
теперь дополнительно проверяем переменную DISPLAY - если она есть,
значит графическая среда доступна и можно запускать UI
2026-05-05 09:36:01 +00:00
SashegDev 300ce4b60b feat(ui): добавляем анимированный прогресс-бар при обновлении сборки
- обновили updateInstance() с поддержкой SSE для реального прогресса
- добавили showAnimatedProgress() и updateAnimatedProgress() методы
- добавили CSS анимацию shimmer для прогресс-бара
- теперь показывает: файл X из Y и процент текущего файла
2026-05-05 08:57:48 +00:00
7 changed files with 248 additions and 22 deletions
+40 -1
View File
@@ -42,12 +42,21 @@
<exclude>META-INF/*.RSA</exclude> <exclude>META-INF/*.RSA</exclude>
</excludes> </excludes>
</filter> </filter>
<filter>
<artifact>org.openjfx:*</artifact>
<excludes>
<exclude>**/*</exclude>
</excludes>
</filter>
</filters> </filters>
<dependencySet> <dependencySet>
<outputDirectory>/</outputDirectory> <outputDirectory>/</outputDirectory>
<useProjectArtifact>false</useProjectArtifact> <useProjectArtifact>false</useProjectArtifact>
<unpack>true</unpack> <unpack>true</unpack>
<scope>runtime</scope> <scope>runtime</scope>
<excludes>
<exclude>org.openjfx:*</exclude>
</excludes>
</dependencySet> </dependencySet>
</configuration> </configuration>
</execution> </execution>
@@ -85,11 +94,16 @@
<configuration> <configuration>
<outfile>../server/builds/ZernMCLauncher-${project.version}.exe</outfile> <outfile>../server/builds/ZernMCLauncher-${project.version}.exe</outfile>
<jar>../server/builds/ZernMCLauncher.jar</jar> <jar>../server/builds/ZernMCLauncher.jar</jar>
<headerType>console</headerType> <headerType>gui</headerType>
<dontWrapJar>false</dontWrapJar> <dontWrapJar>false</dontWrapJar>
<jre> <jre>
<path>jre21</path> <path>jre21</path>
<minVersion>21</minVersion> <minVersion>21</minVersion>
<opts>
<opt>--module-path=lib-javafx</opt>
<opt>--add-modules=javafx.controls,javafx.web</opt>
<opt>--add-reads=javafx.graphics=ALL-UNNAMED</opt>
</opts>
</jre> </jre>
<versionInfo> <versionInfo>
<fileVersion>${project.version}.0</fileVersion> <fileVersion>${project.version}.0</fileVersion>
@@ -105,6 +119,9 @@
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
<configuration>
<skip>${skip.launch4j}</skip>
</configuration>
</plugin> </plugin>
<plugin> <plugin>
<artifactId>maven-antrun-plugin</artifactId> <artifactId>maven-antrun-plugin</artifactId>
@@ -121,6 +138,9 @@
<copy> <copy>
<fileset /> <fileset />
</copy> </copy>
<copy>
<fileset />
</copy>
<zip /> <zip />
</target> </target>
</configuration> </configuration>
@@ -146,6 +166,22 @@
<server.url>http://87.120.187.36:1582</server.url> <server.url>http://87.120.187.36:1582</server.url>
</properties> </properties>
</profile> </profile>
<profile>
<id>win</id>
<properties>
<os.suffix>win</os.suffix>
<javafx.classifier>win</javafx.classifier>
<skip.launch4j>false</skip.launch4j>
</properties>
</profile>
<profile>
<id>linux</id>
<properties>
<os.suffix>linux</os.suffix>
<javafx.classifier>linux</javafx.classifier>
<skip.launch4j>true</skip.launch4j>
</properties>
</profile>
</profiles> </profiles>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -174,7 +210,10 @@
<mainClass>me.sashegdev.zernmc.launcher.Main</mainClass> <mainClass>me.sashegdev.zernmc.launcher.Main</mainClass>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<project.organization.name>ZernMC</project.organization.name> <project.organization.name>ZernMC</project.organization.name>
<javafx.classifier>win</javafx.classifier>
<skip.launch4j>false</skip.launch4j>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
<os.suffix>win</os.suffix>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.inceptionYear>2026</project.inceptionYear> <project.inceptionYear>2026</project.inceptionYear>
</properties> </properties>
+84 -6
View File
@@ -17,6 +17,9 @@
<project.inceptionYear>2026</project.inceptionYear> <project.inceptionYear>2026</project.inceptionYear>
<project.description>ZernMC Launcher - just a minimalistic launcher by SashegDev</project.description> <project.description>ZernMC Launcher - just a minimalistic launcher by SashegDev</project.description>
<mainClass>me.sashegdev.zernmc.launcher.Main</mainClass> <mainClass>me.sashegdev.zernmc.launcher.Main</mainClass>
<javafx.classifier>win</javafx.classifier>
<os.suffix>win</os.suffix>
<skip.launch4j>false</skip.launch4j>
</properties> </properties>
<dependencies> <dependencies>
@@ -74,11 +77,27 @@
<groupId>org.openjfx</groupId> <groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId> <artifactId>javafx-controls</artifactId>
<version>21.0.2</version> <version>21.0.2</version>
<classifier>win</classifier>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.openjfx</groupId> <groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId> <artifactId>javafx-web</artifactId>
<version>21.0.2</version> <version>21.0.2</version>
<classifier>win</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>21.0.2</version>
<classifier>linux</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>21.0.2</version>
<classifier>linux</classifier>
<scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
@@ -130,12 +149,22 @@
<exclude>META-INF/*.RSA</exclude> <exclude>META-INF/*.RSA</exclude>
</excludes> </excludes>
</filter> </filter>
<!-- Исключаем JavaFX из shade полностью (он будет в lib-javafx) -->
<filter>
<artifact>org.openjfx:*</artifact>
<excludes>
<exclude>**/*</exclude>
</excludes>
</filter>
</filters> </filters>
<dependencySet> <dependencySet>
<outputDirectory>/</outputDirectory> <outputDirectory>/</outputDirectory>
<useProjectArtifact>false</useProjectArtifact> <useProjectArtifact>false</useProjectArtifact>
<unpack>true</unpack> <unpack>true</unpack>
<scope>runtime</scope> <scope>runtime</scope>
<excludes>
<exclude>org.openjfx:*</exclude>
</excludes>
</dependencySet> </dependencySet>
</configuration> </configuration>
</execution> </execution>
@@ -163,11 +192,14 @@
</executions> </executions>
</plugin> </plugin>
<!-- Launch4j для создания .exe --> <!-- Launch4j для создания .exe (только для Windows) -->
<plugin> <plugin>
<groupId>com.akathist.maven.plugins.launch4j</groupId> <groupId>com.akathist.maven.plugins.launch4j</groupId>
<artifactId>launch4j-maven-plugin</artifactId> <artifactId>launch4j-maven-plugin</artifactId>
<version>2.5.0</version> <version>2.5.0</version>
<configuration>
<skip>${skip.launch4j}</skip>
</configuration>
<executions> <executions>
<execution> <execution>
<id>l4j</id> <id>l4j</id>
@@ -178,11 +210,16 @@
<configuration> <configuration>
<outfile>../server/builds/ZernMCLauncher-${project.version}.exe</outfile> <outfile>../server/builds/ZernMCLauncher-${project.version}.exe</outfile>
<jar>../server/builds/ZernMCLauncher.jar</jar> <jar>../server/builds/ZernMCLauncher.jar</jar>
<headerType>console</headerType> <headerType>gui</headerType>
<dontWrapJar>false</dontWrapJar> <dontWrapJar>false</dontWrapJar>
<jre> <jre>
<path>jre21</path> <path>jre21</path>
<minVersion>21</minVersion> <minVersion>21</minVersion>
<opts>
<opt>--module-path=lib-javafx</opt>
<opt>--add-modules=javafx.controls,javafx.web</opt>
<opt>--add-reads=javafx.graphics=ALL-UNNAMED</opt>
</opts>
</jre> </jre>
<versionInfo> <versionInfo>
<fileVersion>${project.version}.0</fileVersion> <fileVersion>${project.version}.0</fileVersion>
@@ -218,11 +255,22 @@
<fileset dir="${user.home}/launcher/jre/jre21"/> <fileset dir="${user.home}/launcher/jre/jre21"/>
</copy> </copy>
<!-- Создаём zip только с .exe и jre21 (без .jar и build.version) --> <!-- Копируем JavaFX JAR в builds -->
<zip destfile="../server/builds/ZernMCLauncher-${project.version}.zip" <copy todir="../server/builds/lib-javafx" overwrite="true">
<fileset dir="${project.build.directory}/lib-javafx"/>
</copy>
<!-- Копируем shell script для Linux -->
<copy file="${project.basedir}/src/main/resources/launcher.sh"
todir="../server/builds"
overwrite="true"/>
<chmod file="../server/builds/launcher.sh" perm="+x"/>
<!-- Создаём zip с .exe, jre21, lib-javafx и launcher.sh (без .jar и build.version) -->
<zip destfile="../server/builds/ZernMCLauncher-${project.version}-${os.suffix}.zip"
basedir="../server/builds" basedir="../server/builds"
includes="ZernMCLauncher.exe,jre21/**" includes="ZernMCLauncher.exe,ZernMCLauncher.jar,jre21/**,lib-javafx/**,launcher.sh"
excludes="*.jar,build.version"/> excludes="build.version"/>
</target> </target>
</configuration> </configuration>
</execution> </execution>
@@ -254,5 +302,35 @@
<server.url>http://87.120.187.36:1582</server.url> <server.url>http://87.120.187.36:1582</server.url>
</properties> </properties>
</profile> </profile>
<!-- ==================== WINDOWS BUILD ==================== -->
<profile>
<id>win</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<properties>
<javafx.classifier>win</javafx.classifier>
<os.suffix>win</os.suffix>
<skip.launch4j>false</skip.launch4j>
</properties>
</profile>
<!-- ==================== LINUX BUILD ==================== -->
<profile>
<id>linux</id>
<activation>
<os>
<family>unix</family>
</os>
</activation>
<properties>
<javafx.classifier>linux</javafx.classifier>
<os.suffix>linux</os.suffix>
<skip.launch4j>true</skip.launch4j>
</properties>
</profile>
</profiles> </profiles>
</project> </project>
@@ -34,6 +34,7 @@ public class Main {
startWebUI(args); startWebUI(args);
} catch (Exception e) { } catch (Exception e) {
System.err.println(ZAnsi.red("UI не запустился: " + e.getMessage())); System.err.println(ZAnsi.red("UI не запустился: " + e.getMessage()));
e.printStackTrace();
System.out.println(ZAnsi.yellow("Переключаюсь на режим TUI...")); System.out.println(ZAnsi.yellow("Переключаюсь на режим TUI..."));
runTUI(args); runTUI(args);
} }
@@ -70,13 +71,17 @@ public class Main {
// Даем серверу время запуститься // Даем серверу время запуститься
Thread.sleep(1000); Thread.sleep(1000);
// Проверяем headless перед запуском JavaFX // Проверяем headless перед запуском JavaFX (только для не-Windows систем)
if (java.awt.GraphicsEnvironment.isHeadless()) { if (!System.getProperty("os.name").toLowerCase().contains("win")) {
boolean isHeadless = java.awt.GraphicsEnvironment.isHeadless();
String display = System.getenv("DISPLAY");
if (isHeadless && (display == null || display.isEmpty())) {
System.out.println(ZAnsi.yellow("Дисплей недоступен, переключаюсь на TUI...")); System.out.println(ZAnsi.yellow("Дисплей недоступен, переключаюсь на TUI..."));
WebServer.stop(); WebServer.stop();
runTUI(args); runTUI(args);
return; return;
} }
}
// Проверка обновлений лаунчера // Проверка обновлений лаунчера
checkAndAutoUpdateLauncher(); checkAndAutoUpdateLauncher();
@@ -181,12 +186,20 @@ public class Main {
try { try {
String javaPath = System.getProperty("java.home") + "/bin/java"; String javaPath = System.getProperty("java.home") + "/bin/java";
String jarPath = getCurrentJarPath().toAbsolutePath().toString(); String jarPath = getCurrentJarPath().toAbsolutePath().toString();
String launcherDir = jarPath.substring(0, jarPath.lastIndexOf(java.io.File.separator));
String javafxPath = launcherDir + java.io.File.separator + "lib-javafx";
System.out.println(ZAnsi.brightGreen("Перезапуск лаунчера с новой версией...")); System.out.println(ZAnsi.brightGreen("Перезапуск лаунчера с новой версией..."));
new ProcessBuilder(javaPath, "-jar", jarPath) ProcessBuilder pb = new ProcessBuilder(
.inheritIO() javaPath,
.start(); "--module-path=" + javafxPath,
"--add-modules=javafx.controls,javafx.web",
"--add-reads=javafx.graphics=ALL-UNNAMED",
"-jar", jarPath
);
pb.inheritIO();
pb.start();
System.exit(0); System.exit(0);
} catch (Exception e) { } catch (Exception e) {
@@ -17,11 +17,6 @@ public class UIWindow extends Application {
private static int port; private static int port;
public static void start(int port) { public static void start(int port) {
// Backup проверка headless
if (java.awt.GraphicsEnvironment.isHeadless()) {
throw new RuntimeException("Headless environment - no display available");
}
UIWindow.port = port; UIWindow.port = port;
UIWindow.url = "http://localhost:" + port; UIWindow.url = "http://localhost:" + port;
Application.launch(UIWindow.class); Application.launch(UIWindow.class);
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
JAVA_HOME="$SCRIPT_DIR/jre21"
JAVA="$JAVA_HOME/bin/java"
JAVAFX_PATH="$SCRIPT_DIR/lib-javafx"
exec "$JAVA" \
--module-path="$JAVAFX_PATH" \
--add-modules=javafx.controls,javafx.web \
--add-reads=javafx.graphics=ALL-UNNAMED \
-jar "$SCRIPT_DIR/ZernMCLauncher.jar" "$@"
@@ -686,6 +686,30 @@ body {
font-size: 13px; font-size: 13px;
} }
.progress-label {
margin-bottom: 8px;
font-weight: 500;
}
.progress-file {
font-size: 12px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.progress-fill.animated {
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary), var(--accent-primary));
background-size: 200% 100%;
animation: progressShimmer 1.5s ease-in-out infinite;
}
@keyframes progressShimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* ==================== LOADING ==================== */ /* ==================== LOADING ==================== */
.loading-overlay { .loading-overlay {
position: fixed; position: fixed;
+66 -1
View File
@@ -325,7 +325,43 @@ class App {
} }
this.addLog('Обновление сборки...', 'info'); this.addLog('Обновление сборки...', 'info');
this.showProgress('Обновление сборки...'); this.enablePlayButton(false);
const progressContainer = this.showAnimatedProgress('Обновление сборки...');
let eventSource = null;
let progressData = { totalFiles: 0, downloadedFiles: 0 };
try {
eventSource = new EventSource(`/api/instances/${this.currentInstance.name}/install/stream`);
eventSource.onmessage = (e) => {
try {
const data = JSON.parse(e.data);
if (data.phase === 'starting') {
progressData.totalFiles = data.totalFiles || 0;
this.updateAnimatedProgress(progressContainer, `Загрузка: 0/${progressData.totalFiles} файлов`, 5);
} else if (data.phase === 'downloading') {
progressData.downloadedFiles = data.downloadedFiles || 0;
const total = data.totalFiles || progressData.totalFiles || 1;
const percent = Math.round((progressData.downloadedFiles / total) * 100);
const fileName = data.currentFile ? data.currentFile.split('/').pop() : '';
const filePercent = data.filePercent || 0;
this.updateAnimatedProgress(progressContainer,
`Файл ${progressData.downloadedFiles}/${total} (${percent}%)`,
percent,
fileName,
filePercent
);
} else if (data.phase === 'complete') {
this.updateAnimatedProgress(progressContainer, 'Готово!', 100);
} else if (data.phase === 'error') {
this.addLog('Ошибка: ' + (data.message || 'неизвестная ошибка'), 'error');
}
} catch (err) {}
};
} catch (e) {
console.log('SSE not available, using fallback progress');
}
const result = await this.request('/instances/zernmc/install', { const result = await this.request('/instances/zernmc/install', {
method: 'POST', method: 'POST',
@@ -335,6 +371,10 @@ class App {
}) })
}); });
if (eventSource) {
eventSource.close();
}
this.hideProgress(); this.hideProgress();
if (result.success) { if (result.success) {
@@ -362,6 +402,31 @@ class App {
} }
} }
showAnimatedProgress(text) {
const progress = document.getElementById('download-progress');
const progressText = document.getElementById('progress-text');
const progressFill = document.getElementById('progress-fill');
progress.classList.remove('hidden');
progressText.innerHTML = `<div class="progress-label">${text}</div>
<div class="progress-file"></div>`;
progressFill.style.width = '5%';
progressFill.classList.add('animated');
return { container: progress, text: progressText, fill: progressFill };
}
updateAnimatedProgress(progressContainer, text, percent, fileName = '', filePercent = 0) {
const { text: progressText, fill: progressFill } = progressContainer;
if (fileName) {
progressText.innerHTML = `<div class="progress-label">${text}</div>
<div class="progress-file">${fileName} (${filePercent}%)</div>`;
} else {
progressText.innerHTML = `<div class="progress-label">${text}</div>`;
}
progressFill.style.width = percent + '%';
}
// ==================== DOWNLOAD MODAL ==================== // ==================== DOWNLOAD MODAL ====================
async showDownloadModal() { async showDownloadModal() {
document.getElementById('download-modal').classList.remove('hidden'); document.getElementById('download-modal').classList.remove('hidden');