Вместо двух отдельных списков (Workshop ID и Mod ID) — один список модов: галочка включает мод, перетаскивание задаёт порядок загрузки, кнопка убирает пакет вместе со всеми его модами. Чтобы галочкам было за что зацепиться, панель читает mod.info уже скачанных модов: серверу в Mods нужен именно Mod ID, а он лежит внутри пакета мастерской и вручную его приходилось искать в описании мода. Пока мод не скачан, строка помечена и галочка недоступна — Mod ID неизвестен. Один пакет мастерской может содержать несколько модов; теперь они видны отдельными строками и включаются по отдельности. Коллекции: кнопка разворачивает коллекцию Steam в список входящих в неё модов, сохраняя порядок со страницы коллекции. И мод, и коллекция принимаются как ссылкой, так и голым идентификатором. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
291 lines
13 KiB
HTML
291 lines
13 KiB
HTML
<!doctype html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>PZ Manager</title>
|
|
<link rel="stylesheet" href="/style.css">
|
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text y='26' font-size='26'>🧟</text></svg>">
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Экран входа: он же первичная настройка, когда учёток ещё нет. -->
|
|
<div id="auth-screen" class="auth-screen hidden">
|
|
<form id="auth-form" class="auth-card">
|
|
<h1 class="auth-title">🧟 PZ Manager</h1>
|
|
<p id="auth-subtitle" class="auth-subtitle">Управление сервером Project Zomboid</p>
|
|
|
|
<label class="field" id="token-field" hidden>
|
|
<span>Код первичной настройки</span>
|
|
<input type="text" name="token" autocomplete="off" spellcheck="false">
|
|
<small>Код напечатан в журнале менеджера при запуске: <code>journalctl -u pzmanager</code></small>
|
|
</label>
|
|
|
|
<label class="field">
|
|
<span>Логин</span>
|
|
<input type="text" name="login" autocomplete="username" required>
|
|
</label>
|
|
|
|
<label class="field">
|
|
<span>Пароль</span>
|
|
<input type="password" name="password" autocomplete="current-password" required>
|
|
</label>
|
|
|
|
<p id="auth-error" class="error" hidden></p>
|
|
<button type="submit" class="btn btn-primary btn-block" id="auth-submit">Войти</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Основное приложение -->
|
|
<div id="app" class="app hidden">
|
|
<header class="topbar">
|
|
<div class="brand">🧟 <span>PZ Manager</span></div>
|
|
|
|
<div class="status-pill" id="status-pill">
|
|
<span class="dot" id="status-dot"></span>
|
|
<span id="status-text">—</span>
|
|
</div>
|
|
|
|
<label class="profile-picker">
|
|
<span>Профиль</span>
|
|
<select id="profile-select" title="Профиль сервера: свой мир, конфиг и моды"></select>
|
|
</label>
|
|
|
|
<div class="controls">
|
|
<button class="btn btn-start" id="btn-start">Запустить</button>
|
|
<button class="btn btn-stop" id="btn-stop">Остановить</button>
|
|
<button class="btn" id="btn-restart">Перезапустить</button>
|
|
</div>
|
|
|
|
<div class="spacer"></div>
|
|
<span class="user" id="current-user"></span>
|
|
<button class="btn btn-ghost" id="btn-logout">Выйти</button>
|
|
</header>
|
|
|
|
<div id="task-banner" class="task-banner" hidden>
|
|
<span class="spinner"></span>
|
|
<span id="task-name"></span>
|
|
<button class="btn btn-ghost btn-sm" id="btn-task-cancel">Отменить</button>
|
|
</div>
|
|
|
|
<nav class="tabs" id="tabs">
|
|
<button class="tab active" data-tab="overview">Обзор</button>
|
|
<button class="tab" data-tab="console">Консоль</button>
|
|
<button class="tab" data-tab="config">Конфиг сервера</button>
|
|
<button class="tab" data-tab="sandbox">Песочница</button>
|
|
<button class="tab" data-tab="mods">Моды</button>
|
|
<button class="tab" data-tab="backups">Бэкапы</button>
|
|
<button class="tab" data-tab="profiles">Профили</button>
|
|
<button class="tab" data-tab="settings">Настройки</button>
|
|
</nav>
|
|
|
|
<main class="content">
|
|
|
|
<!-- Обзор -->
|
|
<section class="panel active" data-panel="overview">
|
|
<div class="cards">
|
|
<div class="card">
|
|
<h3>Состояние</h3>
|
|
<div class="stat" id="ov-state">—</div>
|
|
<div class="muted" id="ov-profile"></div>
|
|
<div class="muted" id="ov-uptime"></div>
|
|
<div class="muted" id="ov-pid"></div>
|
|
<div class="muted error" id="ov-lastexit"></div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>Игроки онлайн</h3>
|
|
<div class="stat" id="ov-players-count">0</div>
|
|
<ul class="player-list" id="ov-players"></ul>
|
|
</div>
|
|
<div class="card">
|
|
<h3>Процесс сервера</h3>
|
|
<div class="metric"><span>CPU</span><b id="ov-cpu">—</b></div>
|
|
<div class="metric"><span>Память</span><b id="ov-rss">—</b></div>
|
|
<div class="metric"><span>Потоки</span><b id="ov-threads">—</b></div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>Хост</h3>
|
|
<div class="metric"><span>ОЗУ свободно</span><b id="ov-mem">—</b></div>
|
|
<div class="metric"><span>Load average</span><b id="ov-load">—</b></div>
|
|
<div class="metric"><span>Диск свободно</span><b id="ov-disk">—</b></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" id="install-card">
|
|
<h3>Серверные файлы</h3>
|
|
<p id="install-state" class="muted"></p>
|
|
<button class="btn btn-primary" id="btn-install">Установить / обновить через SteamCMD</button>
|
|
<p class="muted small">Сервер должен быть остановлен. Обновление занимает несколько минут — прогресс виден во вкладке «Консоль».</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Консоль -->
|
|
<section class="panel" data-panel="console">
|
|
<div class="console-toolbar">
|
|
<label class="checkbox"><input type="checkbox" id="console-autoscroll" checked> Прокручивать за выводом</label>
|
|
<input type="search" id="console-filter" placeholder="Фильтр по строке…" class="input-sm">
|
|
<button class="btn btn-ghost btn-sm" id="btn-console-clear">Очистить экран</button>
|
|
</div>
|
|
<pre id="console-output" class="console"></pre>
|
|
<form class="console-input" id="console-form">
|
|
<input type="text" id="console-command" placeholder="Команда сервера, например: players, save, quit"
|
|
autocomplete="off" spellcheck="false">
|
|
<button class="btn btn-primary" type="submit">Отправить</button>
|
|
</form>
|
|
<p class="muted small">Полезные команды: <code>players</code>, <code>save</code>, <code>servermsg "текст"</code>,
|
|
<code>kickuser "ник"</code>, <code>banuser "ник"</code>, <code>checkModsNeedUpdate</code>.</p>
|
|
</section>
|
|
|
|
<!-- Конфиг сервера -->
|
|
<section class="panel" data-panel="config">
|
|
<div class="panel-head">
|
|
<div>
|
|
<h2>Конфигурация сервера</h2>
|
|
<p class="muted small" id="config-path"></p>
|
|
</div>
|
|
<div>
|
|
<input type="search" id="config-filter" placeholder="Поиск параметра…" class="input-sm">
|
|
<button class="btn btn-primary" id="btn-config-save">Сохранить</button>
|
|
</div>
|
|
</div>
|
|
<p class="muted" id="config-empty" hidden></p>
|
|
<div id="config-fields" class="fields"></div>
|
|
</section>
|
|
|
|
<!-- Песочница -->
|
|
<section class="panel" data-panel="sandbox">
|
|
<div class="panel-head">
|
|
<div>
|
|
<h2>Настройки песочницы</h2>
|
|
<p class="muted small" id="sandbox-path"></p>
|
|
</div>
|
|
<div>
|
|
<input type="search" id="sandbox-filter" placeholder="Поиск параметра…" class="input-sm">
|
|
<button class="btn btn-primary" id="btn-sandbox-save">Сохранить</button>
|
|
</div>
|
|
</div>
|
|
<p class="muted" id="sandbox-empty" hidden></p>
|
|
<div id="sandbox-fields" class="fields"></div>
|
|
</section>
|
|
|
|
<!-- Моды -->
|
|
<section class="panel" data-panel="mods">
|
|
<div class="panel-head">
|
|
<div>
|
|
<h2>Моды</h2>
|
|
<p class="muted small">Галочка включает мод, перетаскивание задаёт порядок загрузки.
|
|
Сервер сам скачивает моды из мастерской при запуске.</p>
|
|
</div>
|
|
<button class="btn btn-primary" id="btn-mods-save">Сохранить</button>
|
|
</div>
|
|
|
|
<p class="muted" id="mods-empty" hidden></p>
|
|
|
|
<div id="mods-body">
|
|
<form class="mod-add" id="mod-add-form">
|
|
<input type="text" id="mod-input" autocomplete="off"
|
|
placeholder="Ссылка на мод или коллекцию, либо просто ID">
|
|
<button class="btn" type="submit">Добавить мод</button>
|
|
<button class="btn" type="button" id="btn-add-collection">Добавить коллекцию</button>
|
|
</form>
|
|
<p class="muted small">Коллекция разворачивается в список входящих в неё модов.
|
|
Сами файлы сервер скачивает сам при запуске — после этого моды можно включать галочками.</p>
|
|
|
|
<div class="mod-toolbar">
|
|
<span class="muted small" id="mod-summary"></span>
|
|
<div class="spacer"></div>
|
|
<input type="search" id="mod-filter" class="input-sm" placeholder="Поиск по названию…">
|
|
<label class="checkbox"><input type="checkbox" id="mod-only-enabled"> Только включённые</label>
|
|
</div>
|
|
|
|
<ul class="mod-list" id="mod-list"></ul>
|
|
|
|
<h3>Карты (Map)</h3>
|
|
<textarea id="map-list" rows="3" spellcheck="false"
|
|
placeholder="Например: Muldraugh, KY"></textarea>
|
|
<p class="muted small">Каждая карта с новой строки. Первой должна идти карта мода, последней — базовая.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Бэкапы -->
|
|
<section class="panel" data-panel="backups">
|
|
<div class="panel-head">
|
|
<div>
|
|
<h2>Резервные копии</h2>
|
|
<p class="muted small" id="backup-dir"></p>
|
|
<p class="muted small">Бэкап снимается с активного профиля: его мир и конфиги.</p>
|
|
</div>
|
|
<form id="backup-form">
|
|
<input type="text" id="backup-note" placeholder="Пометка (необязательно)" class="input-sm">
|
|
<button class="btn btn-primary" type="submit">Создать бэкап</button>
|
|
</form>
|
|
</div>
|
|
<table class="table">
|
|
<thead><tr><th>Архив</th><th>Профиль</th><th>Размер</th><th>Создан</th><th></th></tr></thead>
|
|
<tbody id="backup-rows"></tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<!-- Профили -->
|
|
<section class="panel" data-panel="profiles">
|
|
<div class="panel-head">
|
|
<div>
|
|
<h2>Профили серверов</h2>
|
|
<p class="muted small">У каждого профиля свой мир, конфиг, песочница и набор модов.
|
|
Одновременно работает один — переключать можно на остановленном сервере.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="profile-cards" class="cards"></div>
|
|
|
|
<h3>Новый профиль</h3>
|
|
<form id="profile-add-form" class="fields">
|
|
<div class="cfg-field">
|
|
<label for="new-profile-id">Идентификатор</label>
|
|
<input type="text" id="new-profile-id" placeholder="modded" autocomplete="off">
|
|
<div class="hint">Латиница, цифры, дефис. Станет именем конфига и папки мира.</div>
|
|
</div>
|
|
<div class="cfg-field">
|
|
<label for="new-profile-title">Название</label>
|
|
<input type="text" id="new-profile-title" placeholder="Сборка с модами" autocomplete="off">
|
|
<div class="hint">Как профиль подписан в панели.</div>
|
|
</div>
|
|
<div class="cfg-field">
|
|
<label for="new-profile-memory">Память JVM</label>
|
|
<input type="text" id="new-profile-memory" placeholder="8g" autocomplete="off">
|
|
<div class="hint">Применяется при запуске этого профиля.</div>
|
|
</div>
|
|
<div class="cfg-field">
|
|
<label for="new-profile-copy">Скопировать настройки из</label>
|
|
<select id="new-profile-copy"></select>
|
|
<div class="hint">Копирует .ini и настройки песочницы. Мир останется новым.</div>
|
|
</div>
|
|
<div class="cfg-field">
|
|
<button class="btn btn-primary" type="submit">Создать профиль</button>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- Настройки -->
|
|
<section class="panel" data-panel="settings">
|
|
<h2>Настройки менеджера</h2>
|
|
<form id="settings-form" class="fields"></form>
|
|
<button class="btn btn-primary" id="btn-settings-save">Сохранить настройки</button>
|
|
|
|
<h2>Пользователи панели</h2>
|
|
<ul class="user-list" id="user-list"></ul>
|
|
<form id="user-add-form" class="mod-add">
|
|
<input type="text" id="new-user-login" placeholder="Логин" autocomplete="off">
|
|
<input type="password" id="new-user-password" placeholder="Пароль (минимум 8 символов)" autocomplete="new-password">
|
|
<button class="btn" type="submit">Добавить пользователя</button>
|
|
</form>
|
|
</section>
|
|
|
|
</main>
|
|
</div>
|
|
|
|
<div id="toasts" class="toasts"></div>
|
|
<script src="/app.js"></script>
|
|
</body>
|
|
</html>
|