Fix nginx proxy start/stop: missing sudo, silent failures, no progress UI

- ProxyManager::sysctl() and reload() now use sudo for local commands —
  www-data cannot run systemctl directly, so start/stop/restart/reload
  were silently failing with permission denied
- Control endpoint now returns success:false when nginx stays stopped
  after a start/restart, or stays running after a stop
- proxyControl() JS shows a loading overlay while the action runs and
  uses error toast when the action reports failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 11:16:41 +00:00
parent db1f6b8bb8
commit 5e75d4cae4
3 changed files with 17 additions and 7 deletions
+8 -2
View File
@@ -40,13 +40,19 @@ try {
$action === 'control' && $method === 'POST' => (function() use ($body) { $action === 'control' && $method === 'POST' => (function() use ($body) {
$act = $body['action'] ?? ''; $act = $body['action'] ?? '';
if (!in_array($act, ['start','stop','restart','reload'])) Response::error('Invalid action', 400); if (!in_array($act, ['start','stop','restart','reload'])) Response::error('Invalid action', 400);
$result = match($act) { $result = match($act) {
'start' => ProxyManager::start(), 'start' => ProxyManager::start(),
'stop' => ProxyManager::stop(), 'stop' => ProxyManager::stop(),
'restart' => ProxyManager::restart(), 'restart' => ProxyManager::restart(),
'reload' => ProxyManager::reload(), 'reload' => ProxyManager::reload(),
}; };
Response::json(['success' => true, 'data' => ['result' => $result, 'running' => ProxyManager::isRunning()]]); $running = ProxyManager::isRunning();
$success = match($act) {
'start', 'restart' => $running,
'stop' => !$running,
'reload' => !str_starts_with($result, 'Config test failed'),
};
Response::json(['success' => $success, 'data' => ['result' => $result, 'running' => $running]]);
})(), })(),
// GET hosts list // GET hosts list
+3 -3
View File
@@ -145,9 +145,9 @@ class ProxyManager {
self::remoteExec('systemctl reload nginx'); self::remoteExec('systemctl reload nginx');
return 'reloaded'; return 'reloaded';
} }
$test = shell_exec('nginx -t 2>&1'); $test = shell_exec('sudo nginx -t 2>&1');
if (strpos($test ?? '', 'successful') === false) return 'Config test failed: ' . $test; if (strpos($test ?? '', 'successful') === false) return 'Config test failed: ' . $test;
shell_exec('systemctl reload nginx 2>/dev/null'); shell_exec('sudo systemctl reload nginx 2>/dev/null');
return 'reloaded'; return 'reloaded';
} }
@@ -565,7 +565,7 @@ BASH;
sleep(1); sleep(1);
return self::isRunning() ? 'running' : 'stopped'; return self::isRunning() ? 'running' : 'stopped';
} }
shell_exec("systemctl {$action} nginx 2>/dev/null"); shell_exec("sudo systemctl {$action} nginx 2>/dev/null");
sleep(1); sleep(1);
return self::isRunning() ? 'running' : 'stopped'; return self::isRunning() ? 'running' : 'stopped';
} }
+6 -2
View File
@@ -2733,9 +2733,13 @@ window.proxyInstall = async () => {
}; };
window.proxyControl = async (action) => { window.proxyControl = async (action) => {
Nova.loading(action.charAt(0).toUpperCase() + action.slice(1) + 'ing nginx…');
const r = await Nova.api('proxy', 'control', { method: 'POST', body: { action } }); const r = await Nova.api('proxy', 'control', { method: 'POST', body: { action } });
Nova.toast(r?.data?.result || r?.message || action + ' done', 'success'); Nova.loadingDone();
setTimeout(() => Nova.loadPage('nginx-proxy', window._novaPages), 800); const ok = r?.success;
const msg = r?.data?.result || r?.message || (ok ? action + ' done' : action + ' failed');
Nova.toast(msg, ok ? 'success' : 'error');
Nova.loadPage('nginx-proxy', window._novaPages);
}; };
window.proxySync = async () => { window.proxySync = async () => {