feat: rewrite workshop_admin UI – remove adapter terminology, make configurations primary

Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/3fc88263-c1c0-46f6-95f1-7070fc6f9d02

Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-04 16:56:38 +00:00 committed by GitHub
parent e799f5ee5d
commit 86f825e388
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 98 additions and 80 deletions

View file

@ -31,9 +31,17 @@ class WorkshopProfileController
public function handle(): void
{
global $db;
$userId = (int)($_SESSION['user_id'] ?? 0);
if (!$db->isAdmin($userId)) {
print_failure($this->lang['error_admin_only'] ?? 'Administrator access required.');
return;
}
echo '<link rel="stylesheet" type="text/css" href="modules/steam_workshop/steam_workshop.css" />';
$action = $_GET['sw_action'] ?? 'profiles';
$action = $_GET['sw_action'] ?? 'list';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$postAction = $_POST['sw_action'] ?? '';
@ -48,6 +56,7 @@ class WorkshopProfileController
}
switch ($action) {
case 'config_form':
case 'profile_form':
$this->handleForm((int)($_GET['profile_id'] ?? 0));
break;

View file

@ -105,54 +105,59 @@ return [
'error_missing_query' => 'Enter a search term before querying the Workshop.',
// -------------------------------------------------------
// Workshop profile admin (WorkshopProfileController)
// Workshop game configuration admin (WorkshopProfileController)
// -------------------------------------------------------
'nav_workshop_profiles' => 'Workshop Profiles (DB)',
'profile_heading_list' => 'Workshop Game Profiles',
'profile_intro' => 'One profile per supported game. Each profile drives mod install and caching behaviour.',
'profile_btn_create' => 'Create Profile',
'profile_list_empty' => 'No Workshop profiles defined yet.',
'config_heading_list' => 'Workshop Game Configurations',
'config_intro' => 'One configuration per supported game. Each configuration controls how SteamCMD downloads and installs Workshop mods for servers of that game type.',
'config_btn_create' => 'Add Game Configuration',
'config_list_empty' => 'No Workshop configurations defined yet. Add one for each game that supports Steam Workshop mods.',
'config_confirm_delete' => 'Delete this Workshop configuration? Servers using it will no longer have Workshop mod support.',
'config_back_list' => 'Back to configurations',
'config_heading_edit' => 'Edit Workshop Configuration: %s',
'config_heading_create' => 'Add Workshop Game Configuration',
'config_steamcmd_heading' => 'How mods are downloaded',
'config_steamcmd_note' => 'Workshop mods are downloaded using SteamCMD with the command: +workshop_download_item {app_id} {mod_id}. The cache path below is where SteamCMD stores downloaded mod files on the agent machine. The install path below is where those files are copied into the game server directory.',
'config_label_app_id' => 'Steam App ID',
'config_hint_app_id' => 'The Steam App ID used with +workshop_download_item, e.g. 107410 for Arma 3',
'config_hint_game_key' => 'Short identifier matching the game XML key, e.g. arma3_linux',
'config_section_copy' => 'Copy / sync method',
'config_hint_launch_tpl' => 'Extra launch parameters added when this game has Workshop mods enabled. E.g. -mod=@{mod_id}',
'config_label_enabled' => 'Configuration enabled (allows servers to use Workshop mods for this game)',
'profile_col_game' => 'Game',
'profile_col_key' => 'Game Key',
'profile_col_method' => 'Copy Method',
'profile_col_method' => 'Install Method',
'profile_col_restart' => 'Restart?',
'profile_col_status' => 'Status',
'profile_confirm_delete' => 'Delete this Workshop profile? This will not affect already-installed server mods.',
'profile_back_adapters' => 'Back to adapter management',
'profile_back_list' => 'Back to profiles',
'profile_heading_edit' => 'Edit Workshop Profile: %s',
'profile_heading_create' => 'Create Workshop Profile',
'profile_saved' => 'Workshop profile saved.',
'profile_save_error' => 'Failed to save Workshop profile.',
'profile_deleted' => 'Workshop profile deleted.',
'profile_delete_error' => 'Failed to delete Workshop profile.',
'profile_not_found' => 'Profile not found.',
'profile_section_basic' => 'Basic info',
'profile_section_paths' => 'Paths & templates',
'profile_section_copy' => 'Copy / sync method',
'profile_section_config' => 'Config & launch parameters',
'profile_section_flags' => 'Flags',
'profile_label_game_name' => 'Game name',
'profile_label_os' => 'Supported OS',
'profile_label_cache_path' => 'Cache path template',
'profile_label_cache_path' => 'SteamCMD cache path template',
'profile_hint_cache_path' => 'Where SteamCMD downloads mods on the agent. E.g. {steamcmd_path}/steamapps/workshop/content/{workshop_app_id}/{mod_id}',
'profile_label_install_path' => 'Install path template',
'profile_hint_install_path' => 'Server-side mod directory. E.g. {server_path}/mods/{mod_folder}',
'profile_label_install_path' => 'Server install path template',
'profile_hint_install_path' => 'Where mod files are placed inside the game server directory. E.g. {server_path}/mods/{mod_folder}',
'profile_label_folder_name' => 'Mod folder name template',
'profile_hint_folder_name' => 'Folder name for each mod. Default: @{mod_id}',
'profile_label_copy_method' => 'Copy method',
'profile_label_install_script'=> 'Custom install script (admin-defined only, optional)',
'profile_hint_folder_name' => 'Folder name for each mod inside the install path. Default: @{mod_id}',
'profile_label_copy_method' => 'Method used to copy mod files from SteamCMD cache to the server',
'profile_label_install_script'=> 'Custom install script (optional, admin-defined)',
'profile_hint_install_script' => 'Only used when copy method is custom_script. Template variables are replaced before execution.',
'profile_label_config_tpl' => 'Config file template (optional)',
'profile_label_launch_tpl' => 'Launch parameter template (optional)',
'profile_label_requires_restart' => 'Restart required after mod install/update',
'profile_label_enabled' => 'Profile enabled',
'profile_label_requires_restart' => 'Server restart required after mod install or update',
'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}',
'profile_saved' => 'Workshop configuration saved.',
'profile_save_error' => 'Failed to save Workshop configuration.',
'profile_deleted' => 'Workshop configuration deleted.',
'profile_delete_error' => 'Failed to delete Workshop configuration.',
'profile_not_found' => 'Configuration not found.',
'button_delete' => 'Delete',
'error_game_key_invalid' => 'Game key may only contain letters, digits, underscores, dots, and hyphens.',
'error_game_name_required' => 'Game name is required.',
'error_app_id_required' => 'Workshop App ID is required (numeric).',
'error_cache_path_required' => 'Cache path template is required.',
'error_install_path_required' => 'Install path template is required.',
'error_cache_path_required' => 'SteamCMD cache path template is required.',
'error_install_path_required' => 'Server install path template is required.',
// -------------------------------------------------------
// User mod management (WorkshopModController)

View file

@ -584,3 +584,24 @@
color: #777;
font-size: 0.9rem;
}
/* Info box used on the configuration form to explain SteamCMD usage */
.sw-info-box {
background: #e8f4fd;
border: 1px solid #b3d7f5;
border-radius: 4px;
padding: 0.75rem 1rem;
margin-bottom: 1.25rem;
font-size: 0.9rem;
}
.sw-info-box strong {
display: block;
margin-bottom: 0.25rem;
}
.sw-info-box p {
margin: 0;
color: #2c5f8a;
}

View file

@ -6,8 +6,8 @@ declare(strict_types=1);
$isEdit = $profileId > 0 && $profile !== null;
$heading = $isEdit
? sprintf($lang['profile_heading_edit'] ?? 'Edit Workshop Profile: %s', htmlspecialchars($profile['game_name'] ?? ''))
: ($lang['profile_heading_create'] ?? 'Create Workshop Profile');
? 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);
@ -15,14 +15,19 @@ $v = static function (string $key, array $profile, string $default = ''): string
$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'];
$methodList = ['rsync' => 'rsync (Linux)', 'robocopy' => 'robocopy (Windows)', 'custom_script' => 'Custom script'];
$curMethod = (string)($profile['copy_method'] ?? 'rsync');
$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}';
?>
<div class="sw-admin sw-profile-form">
<h3><?php echo $heading; ?></h3>
<p><a href="?m=steam_workshop&p=workshop_admin&sw_action=profiles">&larr; <?php echo htmlspecialchars($lang['profile_back_list'] ?? 'Back to profiles'); ?></a></p>
<p><a href="?m=steam_workshop&p=workshop_admin">&larr; <?php echo htmlspecialchars($lang['config_back_list'] ?? 'Back to configurations'); ?></a></p>
<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>
</div>
<form method="post" action="?m=steam_workshop&p=workshop_admin" class="sw-form">
<input type="hidden" name="sw_action" value="profile_save">
@ -34,6 +39,7 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
<div class="sw-form__grid">
<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>
<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' : ''; ?>>
@ -44,7 +50,8 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
required maxlength="255">
</label>
<label>
<?php echo htmlspecialchars($lang['label_adapter_app_id'] ?? 'Workshop App ID'); ?> <em>*</em>
<?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>
<input type="text" name="workshop_app_id"
value="<?php echo $v('workshop_app_id', $profile ?? []); ?>"
pattern="[0-9]+" required maxlength="32">
@ -69,22 +76,22 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
<small class="sw-hint"><?php echo htmlspecialchars($tplVarNote); ?></small>
<label>
<?php echo htmlspecialchars($lang['profile_label_cache_path'] ?? 'Cache path template'); ?> <em>*</em>
<?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>
<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'] ?? 'Install path template'); ?> <em>*</em>
<small><?php echo htmlspecialchars($lang['profile_hint_install_path'] ?? 'Server-side mod directory. E.g. {server_path}/mods/{mod_folder}'); ?></small>
<?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>
<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. Default: @{mod_id}'); ?></small>
<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>
@ -92,9 +99,9 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
<!-- Copy method -->
<fieldset>
<legend><?php echo htmlspecialchars($lang['profile_section_copy'] ?? 'Copy / sync method'); ?></legend>
<legend><?php echo htmlspecialchars($lang['config_section_copy'] ?? 'Copy / sync method'); ?></legend>
<label>
<?php echo htmlspecialchars($lang['profile_label_copy_method'] ?? 'Copy method'); ?>
<?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' : ''; ?>>
@ -105,7 +112,7 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
</label>
<label>
<?php echo htmlspecialchars($lang['profile_label_install_script'] ?? 'Custom install script (admin-defined only, optional)'); ?>
<?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>
@ -120,6 +127,7 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
</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>
<input type="text" name="launch_param_template"
value="<?php echo $v('launch_param_template', $profile ?? []); ?>">
</label>
@ -131,12 +139,12 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
<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'] ?? 'Restart required after mod install/update'); ?></span>
<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['profile_label_enabled'] ?? 'Profile enabled'); ?></span>
<span><?php echo htmlspecialchars($lang['config_label_enabled'] ?? 'Configuration enabled (allows servers to use Workshop mods for this game)'); ?></span>
</label>
</fieldset>
@ -144,7 +152,7 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
<button class="btn primary" type="submit">
<?php echo htmlspecialchars($lang['button_save'] ?? 'Save'); ?>
</button>
<a class="btn" href="?m=steam_workshop&p=workshop_admin&sw_action=profiles">
<a class="btn" href="?m=steam_workshop&p=workshop_admin">
<?php echo htmlspecialchars($lang['button_cancel'] ?? 'Cancel'); ?>
</a>
</div>

View file

@ -5,15 +5,15 @@ declare(strict_types=1);
?>
<div class="sw-admin sw-profiles">
<div class="sw-admin__intro">
<h3><?php echo htmlspecialchars($lang['profile_heading_list'] ?? 'Workshop Game Profiles'); ?></h3>
<p><?php echo htmlspecialchars($lang['profile_intro'] ?? 'One profile per supported game. Each profile drives mod install and caching behaviour.'); ?></p>
<a class="btn primary" href="?m=steam_workshop&p=workshop_admin&sw_action=profile_form">
<?php echo htmlspecialchars($lang['profile_btn_create'] ?? 'Create Profile'); ?>
<h3><?php echo htmlspecialchars($lang['config_heading_list'] ?? 'Workshop Game Configurations'); ?></h3>
<p><?php echo htmlspecialchars($lang['config_intro'] ?? 'One configuration per supported game. Each configuration controls how SteamCMD downloads and installs Workshop mods for servers of that game type.'); ?></p>
<a class="btn primary" href="?m=steam_workshop&p=workshop_admin&sw_action=config_form">
<?php echo htmlspecialchars($lang['config_btn_create'] ?? 'Add Game Configuration'); ?>
</a>
</div>
<?php if (empty($profiles)): ?>
<p class="sw-empty"><?php echo htmlspecialchars($lang['profile_list_empty'] ?? 'No Workshop profiles defined yet.'); ?></p>
<p class="sw-empty"><?php echo htmlspecialchars($lang['config_list_empty'] ?? 'No Workshop configurations defined yet. Add one for each game that supports Steam Workshop mods.'); ?></p>
<?php else: ?>
<table class="table sw-profiles__table">
<thead>
@ -22,7 +22,7 @@ declare(strict_types=1);
<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_method'] ?? 'Copy Method'); ?></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>
<th><?php echo htmlspecialchars($lang['admin_col_actions'] ?? 'Actions'); ?></th>
@ -36,7 +36,7 @@ declare(strict_types=1);
<td><?php echo htmlspecialchars($profile['workshop_app_id']); ?></td>
<td><?php echo htmlspecialchars($profile['supported_os']); ?></td>
<td><?php echo htmlspecialchars($profile['copy_method']); ?></td>
<td><?php echo $profile['requires_restart'] ? '✔' : '✘'; ?></td>
<td><?php echo $profile['requires_restart'] ? '&#10004;' : '&#10008;'; ?></td>
<td>
<?php if ($profile['enabled']): ?>
<span class="sw-badge sw-badge--enabled"><?php echo htmlspecialchars($lang['status_enabled'] ?? 'Enabled'); ?></span>
@ -46,15 +46,15 @@ declare(strict_types=1);
</td>
<td class="sw-actions">
<a class="btn secondary"
href="?m=steam_workshop&p=workshop_admin&sw_action=profile_form&profile_id=<?php echo (int)$profile['id']; ?>">
href="?m=steam_workshop&p=workshop_admin&sw_action=config_form&profile_id=<?php echo (int)$profile['id']; ?>">
<?php echo htmlspecialchars($lang['button_edit'] ?? 'Edit'); ?>
</a>
<form method="post" action="?m=steam_workshop&p=workshop_admin" class="sw-inline-delete">
<input type="hidden" name="sw_action" value="profile_delete">
<input type="hidden" name="profile_id" value="<?php echo (int)$profile['id']; ?>">
<button type="submit" class="btn danger"
onclick="return confirm('<?php echo htmlspecialchars($lang['profile_confirm_delete'] ?? 'Delete this Workshop profile?'); ?>')">
<?php echo htmlspecialchars($lang['button_delete_adapter'] ?? 'Delete'); ?>
onclick="return confirm('<?php echo htmlspecialchars($lang['config_confirm_delete'] ?? 'Delete this Workshop configuration? Servers using it will no longer have Workshop mod support.'); ?>')">
<?php echo htmlspecialchars($lang['button_delete'] ?? 'Delete'); ?>
</button>
</form>
</td>
@ -63,11 +63,4 @@ declare(strict_types=1);
</tbody>
</table>
<?php endif; ?>
<hr>
<p>
<a href="?m=steam_workshop&p=workshop_admin">&larr;
<?php echo htmlspecialchars($lang['profile_back_adapters'] ?? 'Back to adapter management'); ?>
</a>
</p>
</div>

View file

@ -1,7 +1,6 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/controllers/AdminWorkshopController.php';
require_once __DIR__ . '/controllers/WorkshopProfileController.php';
function exec_ogp_module(): void
@ -9,23 +8,6 @@ function exec_ogp_module(): void
global $db;
echo '<h2>' . get_lang('steam_workshop') . '</h2>';
// Route to the DB-driven profile manager when requested
$swAction = $_GET['sw_action'] ?? '';
$profileActions = ['profiles', 'profile_form'];
$postAction = $_POST['sw_action'] ?? '';
$profilePostActions = ['profile_save', 'profile_delete'];
if (in_array($swAction, $profileActions, true) || in_array($postAction, $profilePostActions, true)) {
$controller = new WorkshopProfileController($db);
$controller->handle();
return;
}
// Default: legacy XML adapter manager + tab link to profiles
echo '<p><a class="btn secondary" href="?m=steam_workshop&p=workshop_admin&sw_action=profiles">'
. (function_exists('get_lang') ? get_lang('nav_workshop_profiles') : 'Workshop Profiles')
. '</a></p>';
$controller = new AdminWorkshopController($db);
$controller = new WorkshopProfileController($db);
$controller->handle();
}