Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20795b053d |
@@ -85,9 +85,15 @@ func (s *Server) handleLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// translatable отсеивает вывод самого игрового сервера: он на английском и в
|
||||||
|
// словаре менеджера его нет. Перебирать ради него все шаблоны накладно —
|
||||||
|
// загрузка мира со сборкой модов печатает десятки тысяч строк.
|
||||||
|
func translatable(stream string) bool {
|
||||||
|
return stream == "manager" || stream == "task"
|
||||||
|
}
|
||||||
|
|
||||||
// translateLines переводит строки консоли на язык читающего. Буфер один на
|
// translateLines переводит строки консоли на язык читающего. Буфер один на
|
||||||
// всех, поэтому перевод делается на выходе, у каждого подключения свой. Вывод
|
// всех, поэтому перевод делается на выходе, у каждого подключения свой.
|
||||||
// самого игрового сервера через словарь проходит без изменений: он английский.
|
|
||||||
func translateLines(r *http.Request, lines []pzserver.LogLine) []pzserver.LogLine {
|
func translateLines(r *http.Request, lines []pzserver.LogLine) []pzserver.LogLine {
|
||||||
lang := i18n.FromRequest(r)
|
lang := i18n.FromRequest(r)
|
||||||
if lang == i18n.Default {
|
if lang == i18n.Default {
|
||||||
@@ -95,7 +101,9 @@ func translateLines(r *http.Request, lines []pzserver.LogLine) []pzserver.LogLin
|
|||||||
}
|
}
|
||||||
out := make([]pzserver.LogLine, len(lines))
|
out := make([]pzserver.LogLine, len(lines))
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
|
if translatable(line.Stream) {
|
||||||
line.Text = i18n.T(lang, line.Text)
|
line.Text = i18n.T(lang, line.Text)
|
||||||
|
}
|
||||||
out[i] = line
|
out[i] = line
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
@@ -149,7 +157,9 @@ func (s *Server) handleLogStream(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if translatable(line.Stream) {
|
||||||
line.Text = i18n.T(lang, line.Text)
|
line.Text = i18n.T(lang, line.Text)
|
||||||
|
}
|
||||||
if !writeSSE(w, line) {
|
if !writeSSE(w, line) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// Вывод игрового сервера идёт мимо словаря: он английский, а перебор шаблонов
|
||||||
|
// на десятках тысяч строк загрузки мира стоит дорого.
|
||||||
|
func TestTranslatableOnlyManagerStreams(t *testing.T) {
|
||||||
|
for _, stream := range []string{"manager", "task"} {
|
||||||
|
if !translatable(stream) {
|
||||||
|
t.Errorf("строки потока %q должны переводиться", stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, stream := range []string{"stdout", "stderr", ""} {
|
||||||
|
if translatable(stream) {
|
||||||
|
t.Errorf("строки потока %q переводить незачем", stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
-8
@@ -436,8 +436,8 @@ function handleConsoleMeta(meta) {
|
|||||||
lastSeq = 0;
|
lastSeq = 0;
|
||||||
if (!restarted) return;
|
if (!restarted) return;
|
||||||
|
|
||||||
$("#console-output").innerHTML = "";
|
clearConsoleScreen();
|
||||||
pushConsoleLine("manager", t("console.managerRestarted"));
|
queueConsoleLine("manager", t("console.managerRestarted"));
|
||||||
// Переоткрываем поток: этот запрашивал историю с прежнего номера, а её у
|
// Переоткрываем поток: этот запрашивал историю с прежнего номера, а её у
|
||||||
// нового запуска нет.
|
// нового запуска нет.
|
||||||
openConsoleStream();
|
openConsoleStream();
|
||||||
@@ -451,30 +451,68 @@ function closeConsoleStream() {
|
|||||||
function appendConsoleLine(line) {
|
function appendConsoleLine(line) {
|
||||||
if (line.seq <= lastSeq) return;
|
if (line.seq <= lastSeq) return;
|
||||||
lastSeq = line.seq;
|
lastSeq = line.seq;
|
||||||
pushConsoleLine(line.stream, line.text);
|
queueConsoleLine(line.stream, line.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pushConsoleLine рисует строку на экране. Отдельно от нумерации: свои
|
// Загрузка мира со сборкой модов печатает десятки тысяч строк за минуты:
|
||||||
// сообщения панель показывает без номера из буфера менеджера.
|
// каждое исключение — стек на два десятка строк. Рисовать их по одной нельзя:
|
||||||
function pushConsoleLine(stream, text) {
|
// прокрутка вниз после каждой строки заставляет браузер пересчитывать раскладку
|
||||||
|
// всего списка, и вкладка встаёт колом на десятки секунд. Поэтому строки
|
||||||
|
// копятся и выводятся пачкой раз в кадр.
|
||||||
|
let consoleQueue = [];
|
||||||
|
let consoleFrame = 0;
|
||||||
|
|
||||||
|
// queueConsoleLine ставит строку в очередь на отрисовку. Отдельно от нумерации:
|
||||||
|
// свои сообщения панель показывает без номера из буфера менеджера.
|
||||||
|
function queueConsoleLine(stream, text) {
|
||||||
|
consoleQueue.push({ stream, text });
|
||||||
|
// На скрытой вкладке кадры не идут, и очередь росла бы без предела: держим
|
||||||
|
// столько, сколько всё равно поместится на экране.
|
||||||
|
if (consoleQueue.length > MAX_CONSOLE_LINES * 2) {
|
||||||
|
consoleQueue = consoleQueue.slice(-MAX_CONSOLE_LINES);
|
||||||
|
}
|
||||||
|
if (!consoleFrame) consoleFrame = requestAnimationFrame(flushConsoleQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushConsoleQueue() {
|
||||||
|
consoleFrame = 0;
|
||||||
|
const pending = consoleQueue;
|
||||||
|
consoleQueue = [];
|
||||||
|
if (!pending.length) return;
|
||||||
|
|
||||||
const output = $("#console-output");
|
const output = $("#console-output");
|
||||||
const filter = $("#console-filter").value.trim().toLowerCase();
|
const filter = $("#console-filter").value.trim().toLowerCase();
|
||||||
|
// Из потопа сохранится всё равно только хвост — остальные узлы незачем даже
|
||||||
|
// создавать.
|
||||||
|
const from = Math.max(0, pending.length - MAX_CONSOLE_LINES);
|
||||||
|
|
||||||
|
const batch = document.createDocumentFragment();
|
||||||
|
for (let i = from; i < pending.length; i++) {
|
||||||
|
const { stream, text } = pending[i];
|
||||||
const el = document.createElement("div");
|
const el = document.createElement("div");
|
||||||
el.className = `line-${stream}`;
|
el.className = `line-${stream}`;
|
||||||
el.textContent = text;
|
el.textContent = text;
|
||||||
el.dataset.text = text.toLowerCase();
|
el.dataset.text = text.toLowerCase();
|
||||||
if (filter && !el.dataset.text.includes(filter)) el.hidden = true;
|
if (filter && !el.dataset.text.includes(filter)) el.hidden = true;
|
||||||
output.appendChild(el);
|
batch.appendChild(el);
|
||||||
|
}
|
||||||
|
output.appendChild(batch);
|
||||||
|
|
||||||
while (output.childElementCount > MAX_CONSOLE_LINES) {
|
while (output.childElementCount > MAX_CONSOLE_LINES) {
|
||||||
output.removeChild(output.firstElementChild);
|
output.removeChild(output.firstElementChild);
|
||||||
}
|
}
|
||||||
|
// Прокрутка — один раз на пачку: именно она и стоит дорого.
|
||||||
if ($("#console-autoscroll").checked) {
|
if ($("#console-autoscroll").checked) {
|
||||||
output.scrollTop = output.scrollHeight;
|
output.scrollTop = output.scrollHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clearConsoleScreen убирает и то, что уже нарисовано, и то, что ждёт кадра.
|
||||||
|
function clearConsoleScreen() {
|
||||||
|
consoleQueue = [];
|
||||||
|
$("#console-output").innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
$("#console-filter").addEventListener("input", (event) => {
|
$("#console-filter").addEventListener("input", (event) => {
|
||||||
const filter = event.target.value.trim().toLowerCase();
|
const filter = event.target.value.trim().toLowerCase();
|
||||||
Array.from($("#console-output").children).forEach((line) => {
|
Array.from($("#console-output").children).forEach((line) => {
|
||||||
@@ -484,7 +522,7 @@ $("#console-filter").addEventListener("input", (event) => {
|
|||||||
|
|
||||||
$("#btn-console-clear").addEventListener("click", () => {
|
$("#btn-console-clear").addEventListener("click", () => {
|
||||||
// Чистим только экран: история остаётся в буфере менеджера.
|
// Чистим только экран: история остаётся в буфере менеджера.
|
||||||
$("#console-output").innerHTML = "";
|
clearConsoleScreen();
|
||||||
});
|
});
|
||||||
|
|
||||||
// История отправленных команд: та же логика, что и в обычном терминале.
|
// История отправленных команд: та же логика, что и в обычном терминале.
|
||||||
|
|||||||
Reference in New Issue
Block a user