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:
copilot-swe-agent[bot] 2026-05-04 19:49:36 +00:00 committed by GitHub
parent 199b398543
commit 69f415ad86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 1334 additions and 312 deletions

View file

@ -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 &amp; 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 &amp; templates'); ?></legend>
<legend><?php echo htmlspecialchars($lang['profile_section_paths'] ?? 'Download &amp; 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 &amp; 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'); ?> &rarr;
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'); ?>) &rarr;
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 &amp; 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>

View file

@ -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'] ? '&#10004;' : '&#10008;'; ?></td>
<td>