fix: address code review feedback

- Fix toggle/load_order handlers to use page-reload (not JSON) responses
- Remove dead jsonResponse helper method from WorkshopModController
- Fix robocopy exit code detection using ROBOCOPY_EXIT: sentinel (not text parsing)
- Fix rsync dry-run change detection using RSYNC_EXIT: sentinel
- Remove agentIdFromRemote() stub; pass agentId directly to triggerSteamCmdDownload() logging
- Fix 'enabled' checkbox default in profile_form to use ($profile['enabled'] ?? 1)
- Add missing error_toggle_failed / error_order_failed lang strings

Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/dbeebd0e-e7a5-469d-8a8c-e63193d1ebb0

Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-30 18:06:05 +00:00 committed by GitHub
parent 8eff063a93
commit fd860963d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 59 additions and 46 deletions

View file

@ -233,18 +233,25 @@ class WorkshopModController
$enabled = !empty($_POST['enabled']);
if ($homeId <= 0 || $workshopId === '') {
$this->jsonResponse(['ok' => false, 'error' => 'Missing parameters.']);
print_failure($this->lang['error_missing_params'] ?? 'Missing parameters.');
$this->handleIndex($userId, $isAdmin);
return;
}
$home = $this->getHome($homeId, $userId, $isAdmin);
if ($home === null) {
$this->jsonResponse(['ok' => false, 'error' => 'Access denied.']);
print_failure($this->lang['error_home_not_found'] ?? 'Server not found.');
$this->handleIndex($userId, $isAdmin);
return;
}
$ok = $this->repo->toggleMod($homeId, $workshopId, $enabled);
$this->jsonResponse(['ok' => $ok]);
if (!$ok) {
print_failure($this->lang['error_toggle_failed'] ?? 'Failed to update mod status.');
}
$_GET['home_id'] = $homeId;
$this->handleModsPage($userId, $isAdmin);
}
private function handleLoadOrder(int $userId, bool $isAdmin): void
@ -254,18 +261,25 @@ class WorkshopModController
$order = (int)($_POST['load_order'] ?? 0);
if ($homeId <= 0 || $workshopId === '') {
$this->jsonResponse(['ok' => false, 'error' => 'Missing parameters.']);
print_failure($this->lang['error_missing_params'] ?? 'Missing parameters.');
$this->handleIndex($userId, $isAdmin);
return;
}
$home = $this->getHome($homeId, $userId, $isAdmin);
if ($home === null) {
$this->jsonResponse(['ok' => false, 'error' => 'Access denied.']);
print_failure($this->lang['error_home_not_found'] ?? 'Server not found.');
$this->handleIndex($userId, $isAdmin);
return;
}
$ok = $this->repo->updateLoadOrder($homeId, $workshopId, $order);
$this->jsonResponse(['ok' => $ok]);
if (!$ok) {
print_failure($this->lang['error_order_failed'] ?? 'Failed to update load order.');
}
$_GET['home_id'] = $homeId;
$this->handleModsPage($userId, $isAdmin);
}
private function handleSync(int $userId, bool $isAdmin): void
@ -360,13 +374,6 @@ class WorkshopModController
require __DIR__ . '/../views/' . $view . '.php';
}
/** @param array<string,mixed> $data */
private function jsonResponse(array $data): void
{
header('Content-Type: application/json');
echo json_encode($data);
}
private function loadLang(): array
{
$file = __DIR__ . '/../lang/en_US.php';

View file

@ -192,5 +192,7 @@ return [
'error_missing_params' => 'Missing required parameters.',
'error_no_profile' => 'No Workshop profile configured for this game.',
'error_mod_not_found' => 'Mod or profile not found.',
'error_toggle_failed' => 'Failed to update mod status.',
'error_order_failed' => 'Failed to update load order.',
];

View file

@ -94,7 +94,7 @@ class WorkshopInstaller
if ($cacheEntry === null || ($cacheEntry['status'] ?? '') !== 'cached') {
$log[] = 'Cache MISS triggering SteamCMD download on agent.';
$downloadResult = $this->triggerSteamCmdDownload(
$remote, $appId, $workshopId, $steamCmdPath, $cachePath, $log
$remote, $agentId, $appId, $workshopId, $steamCmdPath, $cachePath, $log
);
if (!$downloadResult) {
@ -273,6 +273,7 @@ class WorkshopInstaller
*/
private function triggerSteamCmdDownload(
object $remote,
int $agentId,
string $appId,
string $workshopId,
string $steamCmdPath,
@ -292,13 +293,13 @@ class WorkshopInstaller
]);
$log[] = "SteamCMD start: {$cmd}";
$this->writeLog("STEAMCMD START agent={$this->agentIdFromRemote($remote)} app={$appId} mod={$workshopId}");
$this->writeLog("STEAMCMD START agent={$agentId} app={$appId} mod={$workshopId}");
$output = $remote->exec($cmd);
if ($output === null) {
$log[] = 'SteamCMD: no response from agent (command may still be running).';
$this->writeLog("STEAMCMD NO_RESPONSE app={$appId} mod={$workshopId}");
$this->writeLog("STEAMCMD NO_RESPONSE agent={$agentId} app={$appId} mod={$workshopId}");
// Treat as unknown check file existence
} else {
$log[] = 'SteamCMD output: ' . substr((string)$output, 0, 500);
@ -307,11 +308,11 @@ class WorkshopInstaller
// Verify the download succeeded by checking for the cache path on the agent
$exists = $remote->rfile_exists($cachePath);
if ($exists === 1) {
$this->writeLog("STEAMCMD SUCCESS app={$appId} mod={$workshopId} path={$cachePath}");
$this->writeLog("STEAMCMD SUCCESS agent={$agentId} app={$appId} mod={$workshopId} path={$cachePath}");
return true;
}
$this->writeLog("STEAMCMD FAILURE app={$appId} mod={$workshopId} path={$cachePath}");
$this->writeLog("STEAMCMD FAILURE agent={$agentId} app={$appId} mod={$workshopId} path={$cachePath}");
return false;
}
@ -332,27 +333,33 @@ class WorkshopInstaller
$log[] = "Pre-start compare: cache={$cachePath} dest={$installPath} method={$copyMethod}";
if ($copyMethod === 'rsync') {
// Dry-run: any output lines (beyond the exit sentinel) mean changes exist
$cmd = sprintf(
'rsync -rcn --delete %s %s 2>/dev/null; echo "EXIT:$?"',
'rsync -rcn --delete %s %s 2>/dev/null; echo "RSYNC_EXIT:$?"',
escapeshellarg(rtrim($cachePath, '/') . '/'),
escapeshellarg(rtrim($installPath, '/') . '/')
);
$out = (string)$remote->exec($cmd);
// If rsync dry-run produces file list output, changes exist
$hasChanges = preg_match('/\S/', preg_replace('/EXIT:\d+\s*$/', '', $out) ?? '') === 1;
return $hasChanges;
$out = (string)$remote->exec($cmd);
// Strip the exit line, then check for any non-whitespace output
$body = preg_replace('/RSYNC_EXIT:\d+\s*$/', '', $out) ?? '';
return preg_match('/\S/', $body) === 1;
}
if ($copyMethod === 'robocopy') {
// Robocopy /L = list only, /MIR = mirror, /NJH /NJS = no headers
// List-only mode: robocopy exit code 0 = no differences, 1+ = changes or errors.
// Embed the exit code in output so we can read it back via exec().
$cmd = sprintf(
'robocopy /L /MIR /NJH /NJS %s %s',
'robocopy /L /MIR /NJH /NJS %s %s; echo "ROBOCOPY_EXIT:$LASTEXITCODE"',
escapeshellarg($cachePath),
escapeshellarg($installPath)
);
$out = (string)$remote->exec($cmd);
// Exit code 0 = no changes, 1+ = changes
return trim($out) !== '' && !preg_match('/\bNo new\b/i', $out);
$out = (string)$remote->exec($cmd);
if (preg_match('/ROBOCOPY_EXIT:(\d+)/', $out, $m)) {
// 0 = no change; 17 = informational (changes found); 8+ = error
return (int)$m[1] !== 0;
}
// If we cannot determine, assume sync is needed
return true;
}
// custom_script: always sync
@ -392,7 +399,7 @@ class WorkshopInstaller
);
} elseif ($copyMethod === 'robocopy') {
$cmd = sprintf(
'robocopy /MIR /NJH /NJS %s %s; echo "ROBOCOPY EXIT:$LASTEXITCODE"',
'robocopy /MIR /NJH /NJS %s %s; echo "ROBOCOPY_EXIT:$LASTEXITCODE"',
escapeshellarg($cachePath),
escapeshellarg($installPath)
);
@ -419,17 +426,20 @@ class WorkshopInstaller
$out = (string)$remote->exec($cmd);
$log[] = 'Sync output: ' . substr($out, 0, 500);
// Check exit code hint embedded in output
if (preg_match('/EXIT:(\d+)/', $out, $m)) {
$code = (int)$m[1];
// robocopy exit codes 0..7 are success/info, 8+ are errors
if ($copyMethod === 'robocopy') {
$ok = $code < 8;
// Determine success from embedded exit code sentinel
if ($copyMethod === 'robocopy') {
if (preg_match('/ROBOCOPY_EXIT:(\d+)/', $out, $m)) {
// 07 = success/informational; 8+ = error
$ok = (int)$m[1] < 8;
} else {
$ok = $code === 0;
$ok = true; // assume success if no code extracted
}
} else {
$ok = true; // assume success if no code
if (preg_match('/EXIT:(\d+)/', $out, $m)) {
$ok = (int)$m[1] === 0;
} else {
$ok = true;
}
}
if ($ok) {
@ -471,12 +481,6 @@ class WorkshopInstaller
return 'linux';
}
private function agentIdFromRemote(object $remote): string
{
// OGPRemoteLibrary stores host/port; use reflection-free fallback
return 'unknown';
}
private function writeLog(string $message): void
{
$file = $this->logDir . '/workshop_install.log';

View file

@ -135,7 +135,7 @@ $tplVarNote = $lang['profile_template_vars'] ?? 'Available: {home_id} {agent_id
</label>
<label class="sw-checkbox">
<input type="checkbox" name="enabled" value="1"
<?php echo (!isset($profile['enabled']) || !empty($profile['enabled'])) ? 'checked' : ''; ?>>
<?php echo ($profile['enabled'] ?? 1) ? 'checked' : ''; ?>>
<span><?php echo htmlspecialchars($lang['profile_label_enabled'] ?? 'Profile enabled'); ?></span>
</label>
</fieldset>

View file

@ -203,14 +203,14 @@ $baseAction = '?m=steam_workshop&p=main';
<script>
/* Simple toggle / order auto-submit for the mods table */
document.addEventListener('DOMContentLoaded', function () {
// Toggle enable/disable via form submit
// 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();
});
});
// Load order auto-submit on blur
// Load order: submit on change (blur triggers faster than enter on number inputs)
document.querySelectorAll('.js-ws-order').forEach(function (inp) {
inp.addEventListener('change', function () {
inp.closest('form').submit();