Строчные ссылки на файлы модов и своё ожидание готовности
CI / test (push) Successful in 24s
Release / release (push) Successful in 28s

Игра приводит путь к вложенным файлам анимаций в нижний регистр целиком,
вместе с именем папки мода. На Linux такие файлы не находятся, и сервер
сыплет в лог сотнями FileNotFoundException. Кнопка на вкладке «Моды»
раскладывает рядом строчные симлинки: файлы модов не меняются, повторный
запуск после докачки безопасен, а на файловых системах без различия
регистра кнопка честно говорит, что делать нечего.

Ожидание строки о готовности стало настройкой профиля со значением по
умолчанию 30 минут вместо общих 15. Сборке на три сотни модов пятнадцати
не хватает, и панель объявляла бы работающим сервер, который ещё никого
не пустит.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 17:30:40 +03:00
co-authored by Claude Opus 5
parent e0c33706b3
commit 5d9bedaad2
11 changed files with 511 additions and 38 deletions
+28 -2
View File
@@ -384,6 +384,19 @@ $("#btn-install").addEventListener("click", async () => {
}
});
// Строчные ссылки на файлы модов: лечит FileNotFoundException в анимациях на
// файловых системах, различающих регистр. Задача фоновая — вывод идёт в консоль.
$("#btn-mods-casefix").addEventListener("click", async () => {
if (!confirm(t("mods.caseConfirm"))) return;
try {
await post("/api/mods/casefix");
ok(t("mods.caseStarted"));
refreshStatus();
} catch (error) {
fail(error);
}
});
$("#btn-task-cancel").addEventListener("click", async () => {
try {
await post("/api/task/cancel");
@@ -1475,11 +1488,16 @@ $("#btn-mods-save").addEventListener("click", async () => {
// Профили серверов
// ---------------------------------------------------------------------------
let profiles = { list: [], active: "" };
let profiles = { list: [], active: "", default_startup_timeout_min: 0 };
async function loadProfiles() {
const data = await get("/api/profiles");
profiles = { list: data.profiles || [], active: data.active };
profiles = {
list: data.profiles || [],
active: data.active,
// Значение по умолчанию показываем подсказкой в пустом поле.
default_startup_timeout_min: data.default_startup_timeout_min || 0,
};
renderProfileSelect();
renderProfileCards();
@@ -1522,6 +1540,13 @@ function renderProfileCards() {
placeholder="${t("profiles.memoryMinPlaceholder")}">
<small>${t("profiles.memoryMinHint")}</small>
</label>
<label class="field">
<span>${t("profiles.startupTimeout")}</span>
<input type="number" min="1" max="180" data-field="startup_timeout_min"
value="${escapeHTML(String(p.startup_timeout_min || ""))}"
placeholder="${escapeHTML(String(profiles.default_startup_timeout_min || ""))}">
<small>${t("profiles.startupTimeoutHint")}</small>
</label>
<label class="field">
<span>${t("profiles.adminPassword")}</span>
<input type="password" data-field="admin_password" autocomplete="new-password"
@@ -1592,6 +1617,7 @@ $("#profile-cards").addEventListener("click", async (event) => {
title: field("title"),
java_memory: field("java_memory"),
java_memory_min: field("java_memory_min"),
startup_timeout_min: Number(field("startup_timeout_min")) || 0,
admin_password: field("admin_password"),
});
ok(t("profiles.saved"));