diff --git a/internal/pzserver/logbuf.go b/internal/pzserver/logbuf.go index 0289582..b59484e 100644 --- a/internal/pzserver/logbuf.go +++ b/internal/pzserver/logbuf.go @@ -1,6 +1,20 @@ package pzserver -import "sync" +import ( + "regexp" + "sync" +) + +// ansiRe ловит управляющие последовательности терминала: SteamCMD и сам +// сервер раскрашивают вывод, а в браузере эти коды превращаются в мусор +// вида "[0mOK". Первый вариант — CSI (цвета, перемещение курсора), второй — +// всё остальное, что начинается с ESC. +var ansiRe = regexp.MustCompile("\x1b\\[[0-9;?]*[ -/]*[@-~]|\x1b.") + +// stripANSI убирает управляющие последовательности из строки вывода. +func stripANSI(text string) string { + return ansiRe.ReplaceAllString(text, "") +} // LogLine — одна строка консоли сервера с монотонным номером, по которому // веб-клиент понимает, что он пропустил, и откуда догружать. @@ -33,6 +47,10 @@ func newLogBuffer(capacity int) *logBuffer { // append кладёт строку в буфер и рассылает подписчикам. func (b *logBuffer) append(line LogLine) { + // Чистим здесь, а не у каждого источника: через буфер проходят и вывод + // сервера, и SteamCMD, и сообщения самой панели. + line.Text = stripANSI(line.Text) + b.mu.Lock() line.Seq = b.nextSeq b.nextSeq++ diff --git a/internal/pzserver/logbuf_test.go b/internal/pzserver/logbuf_test.go new file mode 100644 index 0000000..1e0953c --- /dev/null +++ b/internal/pzserver/logbuf_test.go @@ -0,0 +1,41 @@ +package pzserver + +import "testing" + +// TestStripANSI берёт строки ровно в том виде, в каком их печатает SteamCMD: +// в браузере коды раскраски превращались в мусор вроде "[0mOK". +func TestStripANSI(t *testing.T) { + cases := map[string]string{ + "\x1b[0mConnecting anonymously to Steam Public...\x1b[0mOK": "Connecting anonymously to Steam Public...OK", + "\x1b[0m Update state (0x3) reconfiguring, progress: 0.00": " Update state (0x3) reconfiguring, progress: 0.00", + "\x1b[1;32mSuccess!\x1b[0m": "Success!", + // Перемещение курсора и очистка строки — тоже CSI. + "\x1b[2K\x1b[1GЗагрузка": "Загрузка", + // Обычные строки не должны меняться. + "[----] Installing update...": "[----] Installing update...", + "Steam Console Client (c) Valve": "Steam Console Client (c) Valve", + "Мир сохранён, игроков онлайн: 3": "Мир сохранён, игроков онлайн: 3", + "": "", + } + + for input, want := range cases { + if got := stripANSI(input); got != want { + t.Errorf("stripANSI(%q) = %q, ожидалось %q", input, got, want) + } + } +} + +// TestAppendStripsANSI проверяет, что чистка стоит на общем пути: через буфер +// идёт и вывод сервера, и SteamCMD, и сообщения самой панели. +func TestAppendStripsANSI(t *testing.T) { + buf := newLogBuffer(10) + buf.append(LogLine{Stream: "task", Text: "\x1b[0mWaiting for user info...\x1b[0mOK"}) + + lines := buf.since(0) + if len(lines) != 1 { + t.Fatalf("строк в буфере: %d", len(lines)) + } + if lines[0].Text != "Waiting for user info...OK" { + t.Errorf("в буфер попала строка с кодами: %q", lines[0].Text) + } +} diff --git a/web/static/app.js b/web/static/app.js index 41e78bd..5f0774d 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -271,6 +271,16 @@ function renderStatus(status) { $("#btn-restart").disabled = !status.installed; $("#btn-install").disabled = running; + // Консоль есть только у живого процесса: у остановленного сервера нет stdin, + // и команда всё равно вернулась бы ошибкой. + const consoleInput = $("#console-command"); + const consoleButton = $("#console-form").querySelector("button[type=submit]"); + consoleInput.disabled = !running; + consoleButton.disabled = !running; + consoleInput.placeholder = running + ? "Команда сервера, например: players, save, quit" + : "Сервер остановлен — консоль принимает команды только на работающем"; + // Профили делят игровые порты и файл запуска, поэтому переключать их можно // только на остановленном сервере. const picker = $("#profile-select"); diff --git a/web/static/index.html b/web/static/index.html index 54056a8..63ce2ff 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -129,8 +129,8 @@

       
- + autocomplete="off" spellcheck="false" disabled> +

Полезные команды: players, save, servermsg "текст", kickuser "ник", banuser "ник", checkModsNeedUpdate.