feat(steam_workshop): production rewrite with full admin profile page and server settings
- module.php: bump db_version to 2; full v2 schema in install_queries[0]; ALTER TABLE migration in install_queries[2] adding 11 new profile columns plus server_workshop_settings table
- WorkshopRepository: expand saveProfile() with all new fields (steam_app_id, steamcmd_login_mode, steamcmd_path, mod_separator, copy_keys, key paths, pre/post scripts, validation_notes); add nullOrStr helper; add server settings CRUD (getServerSettings, saveServerSettings, recordUpdateResult, setUpdateQueued, listQueuedUpdateHomes); update insertOrUpdateMod with custom_folder; update listAllEnabledMods to select new columns
- WorkshopProfileController: full extractProfileData() with all new fields; validateProfileData with folder template check
- views/admin/profile_form.php: full rewrite – all required fields (steam_app_id, workshop_app_id, login mode, SteamCMD path, OS, cache path, install path, folder naming dropdown, launch params, mod separator, copy method, copy keys with JS show/hide, key paths, pre/per-mod/post bash scripts with DayZ example, validation notes, config file template)
- views/admin/profiles.php: updated list view columns (App IDs, Login, Method)
- WorkshopInstaller.php: full rewrite with %var% template support (+ legacy {var} compat); buildTemplateVars() resolves all 14 variables; pre/post script execution; triggerSteamCmdDownload uses new profile fields; copyKeys helper
- WorkshopModController: add save_settings and queue_update POST actions; handleModsPage loads serverSettings + allProfiles
- views/user_workshop_mods.php: full rewrite – server settings form (enable, profile selector, update mode, restart behavior), update status grid (status/error/time/success time), queue update button, mod table with custom_folder column and toggle/order auto-submit
- lang/en_US.php: ~80 new string keys for all v2 fields
- steam_workshop.css: new v2 styles (3-col grid, script textarea, status grid, server settings card, badges)"
Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/e7f0d80d-f775-4794-adbd-cf48b55bc9c1
Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
parent
199b398543
commit
69f415ad86
10 changed files with 1334 additions and 312 deletions
|
|
@ -4,21 +4,28 @@ declare(strict_types=1);
|
|||
/** @var array|null $profile existing row when editing, null when creating */
|
||||
/** @var int $profileId */
|
||||
|
||||
$isEdit = $profileId > 0 && $profile !== null;
|
||||
$heading = $isEdit
|
||||
$isEdit = $profileId > 0 && $profile !== null;
|
||||
$heading = $isEdit
|
||||
? sprintf($lang['config_heading_edit'] ?? 'Edit Workshop Configuration: %s', htmlspecialchars($profile['game_name'] ?? ''))
|
||||
: ($lang['config_heading_create'] ?? 'Add Workshop Game Configuration');
|
||||
|
||||
$v = static function (string $key, array $profile, string $default = ''): string {
|
||||
return htmlspecialchars((string)($profile[$key] ?? $default), ENT_QUOTES);
|
||||
/** Helper: return html-safe value from profile array (or default). */
|
||||
$v = static function (string $key, array $p, string $default = ''): string {
|
||||
return htmlspecialchars((string)($p[$key] ?? $default), ENT_QUOTES);
|
||||
};
|
||||
|
||||
$osList = ['linux' => 'Linux', 'windows' => 'Windows'];
|
||||
$currentOs = array_filter(explode(',', (string)($profile['supported_os'] ?? 'linux')));
|
||||
$methodList = ['rsync' => 'rsync (Linux)', 'robocopy' => 'robocopy (Windows)', 'custom_script' => 'Custom script'];
|
||||
$curMethod = (string)($profile['copy_method'] ?? 'rsync');
|
||||
$osList = ['linux' => 'Linux', 'windows' => 'Windows'];
|
||||
$currentOs = array_filter(explode(',', (string)($profile['supported_os'] ?? 'linux')));
|
||||
$folderFormats = ['@%mod_name%' => '@%mod_name% (mod title)', '@%workshop_id%' => '@%workshop_id% (numeric ID)', 'custom' => 'Custom template'];
|
||||
$curFolderFormat = (string)($profile['folder_naming_format'] ?? '@%workshop_id%');
|
||||
$separatorList = ['semicolon' => 'Semicolon ( ; )', 'comma' => 'Comma ( , )', 'space' => 'Space ( )'];
|
||||
$curSeparator = (string)($profile['mod_separator'] ?? 'semicolon');
|
||||
$copyMethods = ['rsync' => 'rsync (Linux/Unix)', 'copy' => 'cp / basic copy', 'symlink' => 'Symlink'];
|
||||
$curCopyMethod = (string)($profile['copy_method'] ?? 'rsync');
|
||||
$loginModes = ['anonymous' => 'Anonymous (recommended for free mods)', 'account' => 'Configured account (paid games)'];
|
||||
$curLoginMode = (string)($profile['steamcmd_login_mode'] ?? 'anonymous');
|
||||
|
||||
$tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id} {workshop_app_id} {mod_id} {mod_title} {mod_folder} {steamcmd_path} {server_path} {install_path} {cache_path}';
|
||||
$tplVarNote = $lang['profile_template_vars'] ?? 'Variables: %home_id% %server_path% %steam_app_id% %workshop_app_id% %workshop_id% %mod_name% %install_name% %download_path% %source_path% %target_path% %keys_source_path% %keys_target_path% %steamcmd_path%';
|
||||
?>
|
||||
<div class="sw-admin sw-profile-form">
|
||||
<h3><?php echo $heading; ?></h3>
|
||||
|
|
@ -26,36 +33,84 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
|
|||
|
||||
<div class="sw-info-box">
|
||||
<strong><?php echo htmlspecialchars($lang['config_steamcmd_heading'] ?? 'How mods are downloaded'); ?></strong>
|
||||
<p><?php echo htmlspecialchars($lang['config_steamcmd_note'] ?? 'Workshop mods are downloaded using SteamCMD: +workshop_download_item <App ID> <Mod ID>. The cache path below is where SteamCMD stores downloaded content on the agent. The install path is where the mod files are copied into the game server directory.'); ?></p>
|
||||
<p><?php echo htmlspecialchars($lang['config_steamcmd_note'] ?? 'Workshop mods are downloaded using SteamCMD: +workshop_download_item <App ID> <Mod ID>. Configure the paths and scripts below to control how mods are installed for servers of this game type.'); ?></p>
|
||||
<p><strong><?php echo htmlspecialchars($lang['profile_template_vars_heading'] ?? 'Template variables:'); ?></strong><br>
|
||||
<code><?php echo htmlspecialchars($tplVarNote); ?></code></p>
|
||||
</div>
|
||||
|
||||
<form method="post" action="?m=steam_workshop&p=workshop_admin" class="sw-form">
|
||||
<input type="hidden" name="sw_action" value="profile_save">
|
||||
<input type="hidden" name="profile_id" value="<?php echo $profileId; ?>">
|
||||
|
||||
<!-- Basic info -->
|
||||
<!-- Basic identification -->
|
||||
<fieldset>
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_basic'] ?? 'Basic info'); ?></legend>
|
||||
<div class="sw-form__grid">
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_basic'] ?? 'Basic identification'); ?></legend>
|
||||
<div class="sw-form__grid sw-form__grid--3col">
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['label_game_key'] ?? 'Game key'); ?> <em>*</em>
|
||||
<small><?php echo htmlspecialchars($lang['config_hint_game_key'] ?? 'Short identifier matching the game XML key, e.g. arma3_linux'); ?></small>
|
||||
<small><?php echo htmlspecialchars($lang['config_hint_game_key'] ?? 'Short identifier matching the game XML key, e.g. dayz_linux'); ?></small>
|
||||
<input type="text" name="game_key" value="<?php echo $v('game_key', $profile ?? []); ?>"
|
||||
pattern="[A-Za-z0-9_\-.]+" required maxlength="100"
|
||||
<?php echo $isEdit ? 'readonly' : ''; ?>>
|
||||
</label>
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_game_name'] ?? 'Game name'); ?> <em>*</em>
|
||||
<?php echo htmlspecialchars($lang['profile_label_game_name'] ?? 'Game display name'); ?> <em>*</em>
|
||||
<input type="text" name="game_name" value="<?php echo $v('game_name', $profile ?? []); ?>"
|
||||
required maxlength="255">
|
||||
</label>
|
||||
<label class="sw-checkbox" style="align-self:end;padding-bottom:0.75rem;">
|
||||
<input type="checkbox" name="enabled" value="1"
|
||||
<?php echo ($profile['enabled'] ?? 1) ? 'checked' : ''; ?>>
|
||||
<span><?php echo htmlspecialchars($lang['config_label_enabled'] ?? 'Profile enabled'); ?></span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Steam / SteamCMD settings -->
|
||||
<fieldset>
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_steam'] ?? 'Steam & SteamCMD settings'); ?></legend>
|
||||
<div class="sw-form__grid sw-form__grid--3col">
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['config_label_app_id'] ?? 'Steam App ID'); ?> <em>*</em>
|
||||
<small><?php echo htmlspecialchars($lang['config_hint_app_id'] ?? 'The Steam App ID used with +workshop_download_item, e.g. 107410 for Arma 3'); ?></small>
|
||||
<?php echo htmlspecialchars($lang['profile_label_steam_app_id'] ?? 'Steam App ID'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_steam_app_id'] ?? 'The Steam game App ID (e.g. 221100 for DayZ). Used when Steam login is required.'); ?></small>
|
||||
<input type="text" name="steam_app_id"
|
||||
value="<?php echo $v('steam_app_id', $profile ?? []); ?>"
|
||||
pattern="[0-9]*" maxlength="32">
|
||||
</label>
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['config_label_app_id'] ?? 'Workshop App ID'); ?> <em>*</em>
|
||||
<small><?php echo htmlspecialchars($lang['config_hint_app_id'] ?? 'The App ID used with +workshop_download_item, e.g. 221100 for DayZ'); ?></small>
|
||||
<input type="text" name="workshop_app_id"
|
||||
value="<?php echo $v('workshop_app_id', $profile ?? []); ?>"
|
||||
pattern="[0-9]+" required maxlength="32">
|
||||
</label>
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_steamcmd_path'] ?? 'SteamCMD path on agent'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_steamcmd_path'] ?? 'Full path to steamcmd.sh on the remote agent. Leave blank to use the agent default (/home/gameserver/steamcmd/steamcmd.sh).'); ?></small>
|
||||
<input type="text" name="steamcmd_path"
|
||||
value="<?php echo $v('steamcmd_path', $profile ?? []); ?>"
|
||||
placeholder="/home/gameserver/steamcmd/steamcmd.sh" maxlength="512">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="sw-form__grid sw-form__grid--2col">
|
||||
<label class="sw-checkbox">
|
||||
<input type="checkbox" name="steam_login_required" value="1"
|
||||
id="sw-login-required"
|
||||
<?php echo !empty($profile['steam_login_required']) ? 'checked' : ''; ?>>
|
||||
<span><?php echo htmlspecialchars($lang['profile_label_steam_login_required'] ?? 'Steam login required (game is not free / requires ownership)'); ?></span>
|
||||
</label>
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_steamcmd_login_mode'] ?? 'SteamCMD login mode'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_steamcmd_login_mode'] ?? 'Use anonymous for free Workshop mods. Use configured account for games requiring ownership.'); ?></small>
|
||||
<select name="steamcmd_login_mode">
|
||||
<?php foreach ($loginModes as $mVal => $mLabel): ?>
|
||||
<option value="<?php echo $mVal; ?>" <?php echo $curLoginMode === $mVal ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($mLabel); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<fieldset class="sw-form__os-group">
|
||||
|
|
@ -70,81 +125,180 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
|
|||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
<!-- Paths / templates -->
|
||||
<!-- Download & install paths -->
|
||||
<fieldset>
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_paths'] ?? 'Paths & templates'); ?></legend>
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_paths'] ?? 'Download & install paths'); ?></legend>
|
||||
<small class="sw-hint"><?php echo htmlspecialchars($tplVarNote); ?></small>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_cache_path'] ?? 'SteamCMD cache path template'); ?> <em>*</em>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_cache_path'] ?? 'Where SteamCMD downloads mods on the agent. E.g. {steamcmd_path}/steamapps/workshop/content/{workshop_app_id}/{mod_id}'); ?></small>
|
||||
<?php echo htmlspecialchars($lang['profile_label_cache_path'] ?? 'Workshop download/cache path'); ?> <em>*</em>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_cache_path'] ?? 'Where SteamCMD stores downloaded mod content on the agent. E.g. /home/gameserver/steamcmd/steamapps/workshop/content/%workshop_app_id%/%workshop_id%'); ?></small>
|
||||
<input type="text" name="cache_path_template"
|
||||
value="<?php echo $v('cache_path_template', $profile ?? []); ?>" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_install_path'] ?? 'Server install path template'); ?> <em>*</em>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_install_path'] ?? 'Where mod files are placed inside the game server directory. E.g. {server_path}/mods/{mod_folder}'); ?></small>
|
||||
<?php echo htmlspecialchars($lang['profile_label_install_path'] ?? 'Server mod install root'); ?> <em>*</em>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_install_path'] ?? 'Base directory inside the server where mods are installed. E.g. %server_path%/mods/%install_name%'); ?></small>
|
||||
<input type="text" name="install_path_template"
|
||||
value="<?php echo $v('install_path_template', $profile ?? []); ?>" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_folder_name'] ?? 'Mod folder name template'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_folder_name'] ?? 'Folder name for each mod inside the install path. Default: @{mod_id}'); ?></small>
|
||||
<input type="text" name="folder_name_template"
|
||||
value="<?php echo $v('folder_name_template', $profile ?? [], '@{mod_id}'); ?>">
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<!-- Copy method -->
|
||||
<!-- Mod folder naming -->
|
||||
<fieldset>
|
||||
<legend><?php echo htmlspecialchars($lang['config_section_copy'] ?? 'Copy / sync method'); ?></legend>
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_folder'] ?? 'Mod folder naming'); ?></legend>
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_copy_method'] ?? 'Method used to copy mod files from SteamCMD cache to the server'); ?>
|
||||
<select name="copy_method">
|
||||
<?php foreach ($methodList as $mVal => $mLabel): ?>
|
||||
<option value="<?php echo $mVal; ?>" <?php echo $curMethod === $mVal ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($mLabel); ?>
|
||||
<?php echo htmlspecialchars($lang['profile_label_folder_format'] ?? 'Folder naming format'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_folder_format'] ?? 'How each mod folder is named inside the install root.'); ?></small>
|
||||
<select name="folder_naming_format" id="sw-folder-format">
|
||||
<?php foreach ($folderFormats as $fVal => $fLabel): ?>
|
||||
<option value="<?php echo $fVal; ?>" <?php echo $curFolderFormat === $fVal ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($fLabel); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_install_script'] ?? 'Custom install script (optional, admin-defined)'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_install_script'] ?? 'Only used when copy method is custom_script. Template variables are replaced before execution.'); ?></small>
|
||||
<textarea name="install_script" rows="4"><?php echo $v('install_script', $profile ?? []); ?></textarea>
|
||||
</label>
|
||||
<div id="sw-custom-folder-wrap" <?php echo $curFolderFormat !== 'custom' ? 'style="display:none"' : ''; ?>>
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_folder_name'] ?? 'Custom folder name template'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_folder_name'] ?? 'Use %workshop_id% or %mod_name%. E.g. @%workshop_id%'); ?></small>
|
||||
<input type="text" name="folder_name_template"
|
||||
value="<?php echo $v('folder_name_template', $profile ?? [], '@%workshop_id%'); ?>">
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Config / launch params -->
|
||||
<!-- Launch parameters -->
|
||||
<fieldset>
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_config'] ?? 'Config & launch parameters'); ?></legend>
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_launch'] ?? 'Launch parameters'); ?></legend>
|
||||
<div class="sw-form__grid sw-form__grid--2col">
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_mod_launch_param'] ?? 'Mod launch parameter format'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_mod_launch_param'] ?? 'How the full mod list is passed to the server start command. E.g. -mod=%mods%'); ?></small>
|
||||
<input type="text" name="mod_launch_param"
|
||||
value="<?php echo $v('mod_launch_param', $profile ?? []); ?>"
|
||||
placeholder="-mod=%mods%" maxlength="512">
|
||||
</label>
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_mod_separator'] ?? 'Mod separator'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_mod_separator'] ?? 'Character used to join multiple mod folder names in the launch parameter.'); ?></small>
|
||||
<select name="mod_separator">
|
||||
<?php foreach ($separatorList as $sVal => $sLabel): ?>
|
||||
<option value="<?php echo $sVal; ?>" <?php echo $curSeparator === $sVal ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($sLabel); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_config_tpl'] ?? 'Config file template (optional)'); ?>
|
||||
<textarea name="config_file_template" rows="4"><?php echo $v('config_file_template', $profile ?? []); ?></textarea>
|
||||
</label>
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_launch_tpl'] ?? 'Launch parameter template (optional)'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['config_hint_launch_tpl'] ?? 'Extra launch parameters added when this game has Workshop mods enabled. E.g. -mod=@{mod_id}'); ?></small>
|
||||
<?php echo htmlspecialchars($lang['profile_label_launch_tpl'] ?? 'Full launch parameter template (optional)'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['config_hint_launch_tpl'] ?? 'Complete launch parameter string appended to server start. Each mod folder name is joined with the separator above.'); ?></small>
|
||||
<input type="text" name="launch_param_template"
|
||||
value="<?php echo $v('launch_param_template', $profile ?? []); ?>">
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<!-- Flags -->
|
||||
<!-- Copy / sync method -->
|
||||
<fieldset>
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_flags'] ?? 'Flags'); ?></legend>
|
||||
<legend><?php echo htmlspecialchars($lang['config_section_copy'] ?? 'Copy / sync method'); ?></legend>
|
||||
<div class="sw-form__grid sw-form__grid--2col">
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_copy_method'] ?? 'Copy method'); ?>
|
||||
<select name="copy_method">
|
||||
<?php foreach ($copyMethods as $mVal => $mLabel): ?>
|
||||
<option value="<?php echo $mVal; ?>" <?php echo $curCopyMethod === $mVal ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($mLabel); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="sw-checkbox" style="align-self:end;padding-bottom:0.5rem;">
|
||||
<input type="checkbox" name="copy_keys" value="1"
|
||||
id="sw-copy-keys"
|
||||
<?php echo !empty($profile['copy_keys']) ? 'checked' : ''; ?>>
|
||||
<span><?php echo htmlspecialchars($lang['profile_label_copy_keys'] ?? 'Copy mod keys (*.bikey) to server keys directory'); ?></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="sw-key-paths-wrap" <?php echo empty($profile['copy_keys']) ? 'style="display:none"' : ''; ?>>
|
||||
<div class="sw-form__grid sw-form__grid--2col">
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_key_source'] ?? 'Key source path'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_key_source'] ?? 'Path inside the mod cache where key files live. E.g. %source_path%/keys'); ?></small>
|
||||
<input type="text" name="key_source_path"
|
||||
value="<?php echo $v('key_source_path', $profile ?? []); ?>"
|
||||
placeholder="%source_path%/keys">
|
||||
</label>
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_key_dest'] ?? 'Key destination path'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_key_dest'] ?? 'Where keys are copied on the server. E.g. %server_path%/keys'); ?></small>
|
||||
<input type="text" name="key_dest_path"
|
||||
value="<?php echo $v('key_dest_path', $profile ?? []); ?>"
|
||||
placeholder="%server_path%/keys">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Bash scripts -->
|
||||
<fieldset>
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_scripts'] ?? 'Bash scripts'); ?></legend>
|
||||
<div class="sw-info-box sw-info-box--compact">
|
||||
<strong><?php echo htmlspecialchars($lang['profile_scripts_order'] ?? 'Execution order:'); ?></strong>
|
||||
1. <?php echo htmlspecialchars($lang['profile_label_pre_script'] ?? 'Pre-update script'); ?> →
|
||||
2. <?php echo htmlspecialchars($lang['profile_label_install_script'] ?? 'Per-mod install script'); ?> (<?php echo htmlspecialchars($lang['profile_scripts_per_mod'] ?? 'repeated for each mod'); ?>) →
|
||||
3. <?php echo htmlspecialchars($lang['profile_label_post_script'] ?? 'Post-update script'); ?>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_pre_script'] ?? 'Pre-update bash script'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_pre_script'] ?? 'Runs once before any mod is downloaded/installed. Variables: %home_id% %server_path% %workshop_app_id%'); ?></small>
|
||||
<textarea name="pre_update_script" rows="4" class="sw-script-textarea"><?php echo $v('pre_update_script', $profile ?? []); ?></textarea>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_install_script'] ?? 'Per-mod install bash script'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_install_script'] ?? 'Runs once for each mod. All template variables listed above are available.'); ?></small>
|
||||
<details class="sw-example-block">
|
||||
<summary><?php echo htmlspecialchars($lang['profile_script_example_toggle'] ?? 'Show DayZ-style example'); ?></summary>
|
||||
<pre class="sw-code-pre">mkdir -p "%target_path%"
|
||||
rsync -a --delete "%source_path%/" "%target_path%/"
|
||||
if [ -d "%source_path%/keys" ]; then
|
||||
mkdir -p "%keys_target_path%"
|
||||
cp -f "%source_path%/keys/"*.bikey "%keys_target_path%/" 2>/dev/null || true
|
||||
fi</pre>
|
||||
</details>
|
||||
<textarea name="install_script" rows="8" class="sw-script-textarea"><?php echo $v('install_script', $profile ?? []); ?></textarea>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_post_script'] ?? 'Post-update bash script'); ?>
|
||||
<small><?php echo htmlspecialchars($lang['profile_hint_post_script'] ?? 'Runs once after all mods have been installed. Variables: %home_id% %server_path% %workshop_app_id%'); ?></small>
|
||||
<textarea name="post_update_script" rows="4" class="sw-script-textarea"><?php echo $v('post_update_script', $profile ?? []); ?></textarea>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<!-- Options & validation -->
|
||||
<fieldset>
|
||||
<legend><?php echo htmlspecialchars($lang['profile_section_flags'] ?? 'Options & validation'); ?></legend>
|
||||
<label class="sw-checkbox">
|
||||
<input type="checkbox" name="requires_restart" value="1"
|
||||
<?php echo !empty($profile['requires_restart']) ? 'checked' : ''; ?>>
|
||||
<span><?php echo htmlspecialchars($lang['profile_label_requires_restart'] ?? 'Server restart required after mod install or update'); ?></span>
|
||||
</label>
|
||||
<label class="sw-checkbox">
|
||||
<input type="checkbox" name="enabled" value="1"
|
||||
<?php echo ($profile['enabled'] ?? 1) ? 'checked' : ''; ?>>
|
||||
<span><?php echo htmlspecialchars($lang['config_label_enabled'] ?? 'Configuration enabled (allows servers to use Workshop mods for this game)'); ?></span>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_validation_notes'] ?? 'Validation notes / help text (shown to server owners)'); ?>
|
||||
<textarea name="validation_notes" rows="3"><?php echo $v('validation_notes', $profile ?? []); ?></textarea>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['profile_label_config_tpl'] ?? 'Config file template (optional)'); ?>
|
||||
<textarea name="config_file_template" rows="3"><?php echo $v('config_file_template', $profile ?? []); ?></textarea>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
|
|
@ -158,3 +312,25 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Show/hide custom folder template field
|
||||
const formatSel = document.getElementById('sw-folder-format');
|
||||
const customWrap = document.getElementById('sw-custom-folder-wrap');
|
||||
if (formatSel && customWrap) {
|
||||
formatSel.addEventListener('change', function () {
|
||||
customWrap.style.display = this.value === 'custom' ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// Show/hide key path fields
|
||||
const copyKeysChk = document.getElementById('sw-copy-keys');
|
||||
const keyPathsWrap = document.getElementById('sw-key-paths-wrap');
|
||||
if (copyKeysChk && keyPathsWrap) {
|
||||
copyKeysChk.addEventListener('change', function () {
|
||||
keyPathsWrap.style.display = this.checked ? '' : 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ declare(strict_types=1);
|
|||
<tr>
|
||||
<th><?php echo htmlspecialchars($lang['profile_col_game'] ?? 'Game'); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['profile_col_key'] ?? 'Game Key'); ?></th>
|
||||
<th>App ID</th>
|
||||
<th>OS</th>
|
||||
<th><?php echo htmlspecialchars($lang['profile_col_app_ids'] ?? 'App IDs'); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['profile_col_login'] ?? 'Login'); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['profile_col_method'] ?? 'Install Method'); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['profile_col_restart'] ?? 'Restart?'); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['profile_col_status'] ?? 'Status'); ?></th>
|
||||
|
|
@ -33,8 +33,16 @@ declare(strict_types=1);
|
|||
<tr>
|
||||
<td><?php echo htmlspecialchars($profile['game_name']); ?></td>
|
||||
<td><code><?php echo htmlspecialchars($profile['game_key']); ?></code></td>
|
||||
<td><?php echo htmlspecialchars($profile['workshop_app_id']); ?></td>
|
||||
<td><?php echo htmlspecialchars($profile['supported_os']); ?></td>
|
||||
<td>
|
||||
<small><?php echo htmlspecialchars($lang['profile_col_steam'] ?? 'Steam'); ?>:</small> <?php echo htmlspecialchars($profile['steam_app_id'] !== '' ? $profile['steam_app_id'] : '—'); ?><br>
|
||||
<small><?php echo htmlspecialchars($lang['profile_col_workshop'] ?? 'Workshop'); ?>:</small> <?php echo htmlspecialchars($profile['workshop_app_id']); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo htmlspecialchars($profile['steamcmd_login_mode'] ?? 'anonymous'); ?>
|
||||
<?php if (!empty($profile['steam_login_required'])): ?>
|
||||
<span class="sw-badge sw-badge--warning"><?php echo htmlspecialchars($lang['profile_badge_login_required'] ?? 'Login req.'); ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($profile['copy_method']); ?></td>
|
||||
<td><?php echo $profile['requires_restart'] ? '✔' : '✘'; ?></td>
|
||||
<td>
|
||||
|
|
|
|||
|
|
@ -7,22 +7,156 @@ declare(strict_types=1);
|
|||
/** @var string|null $appId */
|
||||
/** @var array[] $installedMods */
|
||||
/** @var array[] $availableMods */
|
||||
/** @var array $serverSettings */
|
||||
/** @var array[] $allProfiles */
|
||||
/** @var bool $isAdmin */
|
||||
|
||||
$homeName = htmlspecialchars($home['home_name'] ?? ('#' . $homeId));
|
||||
$baseAction = '?m=steam_workshop&p=main';
|
||||
|
||||
$wsEnabled = !empty($serverSettings['workshop_enabled']);
|
||||
$curProfileId = (int)($serverSettings['profile_id'] ?? 0);
|
||||
$updateMode = (string)($serverSettings['update_mode'] ?? 'manual');
|
||||
$restartBehav = (string)($serverSettings['restart_behavior'] ?? 'none');
|
||||
$lastStatus = (string)($serverSettings['last_update_status'] ?? '');
|
||||
$lastError = (string)($serverSettings['last_update_error'] ?? '');
|
||||
$lastUpdateTime = (string)($serverSettings['last_update_time'] ?? '');
|
||||
$lastSuccess = (string)($serverSettings['last_success_time'] ?? '');
|
||||
$updateQueued = !empty($serverSettings['update_queued']);
|
||||
|
||||
$updateModes = [
|
||||
'manual' => $lang['update_mode_manual'] ?? 'Manual only',
|
||||
'scheduled' => $lang['update_mode_scheduled'] ?? 'Scheduled',
|
||||
'on_restart' => $lang['update_mode_on_restart'] ?? 'Before server restart',
|
||||
];
|
||||
$restartBehaviors = [
|
||||
'none' => $lang['restart_behavior_none'] ?? 'No restart',
|
||||
'queue' => $lang['restart_behavior_queue'] ?? 'Queue restart',
|
||||
'stop_update_start'=> $lang['restart_behavior_stop'] ?? 'Stop / Update / Start',
|
||||
];
|
||||
|
||||
$statusClass = match($lastStatus) {
|
||||
'success' => 'sw-badge--enabled',
|
||||
'failed' => 'sw-badge--danger',
|
||||
'running' => 'sw-badge--info',
|
||||
'pending' => 'sw-badge--warning',
|
||||
default => '',
|
||||
};
|
||||
?>
|
||||
<div class="sw-user sw-ws-mods">
|
||||
<p><a href="<?php echo $baseAction; ?>">← <?php echo htmlspecialchars($lang['button_cancel'] ?? 'Back'); ?></a></p>
|
||||
<h3><?php echo sprintf(htmlspecialchars($lang['user_workshop_server_heading'] ?? 'Workshop Mods – %s'), $homeName); ?></h3>
|
||||
|
||||
<!-- ── Workshop server settings ── -->
|
||||
<section class="sw-server-settings">
|
||||
<h4><?php echo htmlspecialchars($lang['heading_server_settings'] ?? 'Workshop Settings for this server'); ?></h4>
|
||||
<form method="post" action="<?php echo $baseAction; ?>" class="sw-form sw-settings-form">
|
||||
<input type="hidden" name="ws_action" value="save_settings">
|
||||
<input type="hidden" name="home_id" value="<?php echo $homeId; ?>">
|
||||
|
||||
<div class="sw-form__grid sw-form__grid--2col">
|
||||
<label class="sw-checkbox">
|
||||
<input type="checkbox" name="workshop_enabled" value="1" id="sw-ws-enabled"
|
||||
<?php echo $wsEnabled ? 'checked' : ''; ?>>
|
||||
<span><?php echo htmlspecialchars($lang['label_workshop_enabled'] ?? 'Enable Workshop for this server'); ?></span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['label_select_profile'] ?? 'Workshop game profile'); ?>
|
||||
<select name="profile_id">
|
||||
<option value="0">-- <?php echo htmlspecialchars($lang['label_auto_detect'] ?? 'Auto-detect from game type'); ?> --</option>
|
||||
<?php foreach ((array)$allProfiles as $p): ?>
|
||||
<option value="<?php echo (int)$p['id']; ?>"
|
||||
<?php echo $curProfileId === (int)$p['id'] ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($p['game_name'] . ' (' . $p['workshop_app_id'] . ')'); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['label_update_mode'] ?? 'Update mode'); ?>
|
||||
<select name="update_mode">
|
||||
<?php foreach ($updateModes as $mVal => $mLabel): ?>
|
||||
<option value="<?php echo $mVal; ?>" <?php echo $updateMode === $mVal ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($mLabel); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<?php echo htmlspecialchars($lang['label_restart_behavior'] ?? 'Restart behavior'); ?>
|
||||
<select name="restart_behavior">
|
||||
<?php foreach ($restartBehaviors as $rVal => $rLabel): ?>
|
||||
<option value="<?php echo $rVal; ?>" <?php echo $restartBehav === $rVal ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($rLabel); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="sw-form__actions">
|
||||
<button type="submit" class="btn primary">
|
||||
<?php echo htmlspecialchars($lang['button_save'] ?? 'Save'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Update status summary -->
|
||||
<div class="sw-update-status">
|
||||
<dl class="sw-status-grid">
|
||||
<?php if ($lastStatus !== ''): ?>
|
||||
<dt><?php echo htmlspecialchars($lang['label_last_update_status'] ?? 'Last update status'); ?></dt>
|
||||
<dd><span class="sw-badge <?php echo $statusClass; ?>"><?php echo htmlspecialchars($lastStatus); ?></span></dd>
|
||||
<?php endif; ?>
|
||||
<?php if ($lastUpdateTime !== ''): ?>
|
||||
<dt><?php echo htmlspecialchars($lang['label_last_update_time'] ?? 'Last update time'); ?></dt>
|
||||
<dd><?php echo htmlspecialchars($lastUpdateTime); ?></dd>
|
||||
<?php endif; ?>
|
||||
<?php if ($lastSuccess !== ''): ?>
|
||||
<dt><?php echo htmlspecialchars($lang['label_last_success_time'] ?? 'Last successful update'); ?></dt>
|
||||
<dd><?php echo htmlspecialchars($lastSuccess); ?></dd>
|
||||
<?php endif; ?>
|
||||
<?php if ($lastError !== ''): ?>
|
||||
<dt><?php echo htmlspecialchars($lang['label_last_update_error'] ?? 'Last error'); ?></dt>
|
||||
<dd class="sw-error-text"><code><?php echo htmlspecialchars($lastError); ?></code></dd>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
|
||||
<?php if ($updateQueued): ?>
|
||||
<p class="sw-notice sw-notice--info">
|
||||
<?php echo htmlspecialchars($lang['update_queued_notice'] ?? 'A manual update is queued and will run on the next scheduler cycle.'); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Queue manual update -->
|
||||
<form method="post" action="<?php echo $baseAction; ?>" class="sw-inline">
|
||||
<input type="hidden" name="ws_action" value="queue_update">
|
||||
<input type="hidden" name="home_id" value="<?php echo $homeId; ?>">
|
||||
<button type="submit" class="btn secondary"
|
||||
<?php echo !$wsEnabled ? 'disabled title="Enable Workshop for this server first."' : ''; ?>>
|
||||
<?php echo htmlspecialchars($lang['btn_queue_update'] ?? 'Queue manual update'); ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if ($profile === null): ?>
|
||||
<div class="sw-notice">
|
||||
<p><?php echo htmlspecialchars($lang['no_profile_notice'] ?? 'No Workshop profile is configured for this game. An administrator needs to create one first.'); ?></p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<!-- Installed mods table -->
|
||||
<?php if (!empty($profile['validation_notes'])): ?>
|
||||
<div class="sw-notice sw-notice--info">
|
||||
<strong><?php echo htmlspecialchars($lang['label_admin_notes'] ?? 'Admin notes:'); ?></strong>
|
||||
<?php echo htmlspecialchars($profile['validation_notes']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ── Installed mods table ── -->
|
||||
<h4><?php echo htmlspecialchars($lang['heading_installed_mods'] ?? 'Installed Mods'); ?></h4>
|
||||
<?php if (empty($installedMods)): ?>
|
||||
<p class="sw-empty"><?php echo htmlspecialchars($lang['no_installed_mods'] ?? 'No mods installed yet.'); ?></p>
|
||||
|
|
@ -32,6 +166,7 @@ $baseAction = '?m=steam_workshop&p=main';
|
|||
<tr>
|
||||
<th><?php echo htmlspecialchars($lang['col_mod_id'] ?? 'Workshop ID'); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['col_mod_title'] ?? 'Title'); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['col_mod_folder'] ?? 'Install folder'); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['mods_header_enabled'] ?? 'Enabled'); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['col_load_order'] ?? 'Load order'); ?></th>
|
||||
<th><?php echo htmlspecialchars($lang['admin_col_actions'] ?? 'Actions'); ?></th>
|
||||
|
|
@ -46,6 +181,7 @@ $baseAction = '?m=steam_workshop&p=main';
|
|||
target="_blank" rel="noopener"><?php echo $wid; ?></a>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($mod['title'] ?? $mod['workshop_id']); ?></td>
|
||||
<td><code><?php echo htmlspecialchars($mod['custom_folder'] !== '' ? $mod['custom_folder'] : ($mod['install_path'] ?? '')); ?></code></td>
|
||||
<td>
|
||||
<form method="post" action="<?php echo $baseAction; ?>" class="sw-toggle-form">
|
||||
<input type="hidden" name="ws_action" value="toggle">
|
||||
|
|
@ -97,7 +233,7 @@ $baseAction = '?m=steam_workshop&p=main';
|
|||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Install from cache -->
|
||||
<!-- ── Available cached mods ── -->
|
||||
<?php if (!empty($availableMods)): ?>
|
||||
<h4><?php echo htmlspecialchars($lang['heading_cached_mods'] ?? 'Available Cached Mods (this agent)'); ?></h4>
|
||||
<table class="table sw-ws-mods__cache-table">
|
||||
|
|
@ -132,7 +268,7 @@ $baseAction = '?m=steam_workshop&p=main';
|
|||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Search + install by Workshop ID -->
|
||||
<!-- ── Install by Workshop ID ── -->
|
||||
<h4><?php echo htmlspecialchars($lang['heading_install_mod'] ?? 'Install Mod by Workshop ID'); ?></h4>
|
||||
<form method="post" action="<?php echo $baseAction; ?>" class="sw-form sw-install-form">
|
||||
<input type="hidden" name="ws_action" value="install">
|
||||
|
|
@ -149,9 +285,9 @@ $baseAction = '?m=steam_workshop&p=main';
|
|||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Steam Workshop search widget (reuse existing JS picker) -->
|
||||
<!-- ── Steam Workshop search widget ── -->
|
||||
<?php
|
||||
$scriptPath = (string)($_SERVER['PHP_SELF'] ?? '/index.php');
|
||||
$scriptPath = (string)($_SERVER['PHP_SELF'] ?? '/index.php');
|
||||
$searchEndpoint = sprintf('%s?m=steam_workshop&p=main&action=search&home_id=%d', $scriptPath, $homeId);
|
||||
$langAttrs = [
|
||||
'add' => $lang['mod_picker_action_add'] ?? 'Add',
|
||||
|
|
@ -201,20 +337,15 @@ $baseAction = '?m=steam_workshop&p=main';
|
|||
</div>
|
||||
|
||||
<script>
|
||||
/* Simple toggle / order auto-submit for the mods table */
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Toggle enable/disable: submit the parent form immediately on change
|
||||
document.querySelectorAll('.js-ws-toggle').forEach(function (cb) {
|
||||
cb.addEventListener('change', function () {
|
||||
cb.closest('form').submit();
|
||||
});
|
||||
cb.addEventListener('change', function () { cb.closest('form').submit(); });
|
||||
});
|
||||
|
||||
// Load order: submit on change (blur triggers faster than enter on number inputs)
|
||||
// Load order: submit on change
|
||||
document.querySelectorAll('.js-ws-order').forEach(function (inp) {
|
||||
inp.addEventListener('change', function () {
|
||||
inp.closest('form').submit();
|
||||
});
|
||||
inp.addEventListener('change', function () { inp.closest('form').submit(); });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue