refactor(proxy): streamline proxy status handling and improve dashboard availability checks
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Services\ProxyDashboardCacheService;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class CheckConfiguration
|
class CheckConfiguration
|
||||||
@@ -28,6 +29,8 @@ class CheckConfiguration
|
|||||||
throw new \Exception('Could not generate proxy configuration');
|
throw new \Exception('Could not generate proxy configuration');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProxyDashboardCacheService::isTraefikDashboardAvailableFromConfiguration($server, $proxy_configuration);
|
||||||
|
|
||||||
return $proxy_configuration;
|
return $proxy_configuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,9 +3,9 @@
|
|||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use App\Events\ProxyStatusChanged;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -13,38 +13,12 @@ class CheckProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
/**
|
// It should return if the proxy should be started (true) or not (false)
|
||||||
* Determine if the proxy should be started
|
public function handle(Server $server, $fromUI = false): bool
|
||||||
*
|
|
||||||
* @param bool $fromUI Whether this check is initiated from the UI
|
|
||||||
* @return bool True if proxy should be started, false otherwise
|
|
||||||
*/
|
|
||||||
public function handle(Server $server, bool $fromUI = false): bool
|
|
||||||
{
|
{
|
||||||
// Early validation checks
|
|
||||||
if (! $this->shouldProxyRun($server, $fromUI)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle swarm vs standalone differently
|
|
||||||
if ($server->isSwarm()) {
|
|
||||||
return $this->checkSwarmProxy($server);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->checkStandaloneProxy($server, $fromUI);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic validation to determine if proxy can run at all
|
|
||||||
*/
|
|
||||||
private function shouldProxyRun(Server $server, bool $fromUI): bool
|
|
||||||
{
|
|
||||||
// Server must be functional
|
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build servers don't need proxy
|
|
||||||
if ($server->isBuildServer()) {
|
if ($server->isBuildServer()) {
|
||||||
if ($server->proxy) {
|
if ($server->proxy) {
|
||||||
$server->proxy = null;
|
$server->proxy = null;
|
||||||
@@ -53,308 +27,255 @@ class CheckProxy
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
|
|
||||||
// Check if proxy is disabled or force stopped
|
|
||||||
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) && ! $fromUI) {
|
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) && ! $fromUI) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate proxy configuration
|
|
||||||
if (! $server->isProxyShouldRun()) {
|
if (! $server->isProxyShouldRun()) {
|
||||||
if ($fromUI) {
|
if ($fromUI) {
|
||||||
throw new \Exception('Proxy should not run. You selected the Custom Proxy.');
|
throw new \Exception('Proxy should not run. You selected the Custom Proxy.');
|
||||||
}
|
} else {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Determine proxy container name based on environment
|
||||||
* Check proxy status for swarm mode
|
$proxyContainerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
||||||
*/
|
|
||||||
private function checkSwarmProxy(Server $server): bool
|
|
||||||
{
|
|
||||||
$proxyContainerName = 'coolify-proxy_traefik';
|
|
||||||
$status = getContainerStatus($server, $proxyContainerName);
|
|
||||||
|
|
||||||
// Update status if changed
|
if ($server->isSwarm()) {
|
||||||
if ($server->proxy->status !== $status) {
|
$status = getContainerStatus($server, $proxyContainerName);
|
||||||
$server->proxy->set('status', $status);
|
$server->proxy->set('status', $status);
|
||||||
$server->save();
|
$server->save();
|
||||||
}
|
|
||||||
|
|
||||||
// If running, no need to start
|
|
||||||
return $status !== 'running';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check proxy status for standalone mode
|
|
||||||
*/
|
|
||||||
private function checkStandaloneProxy(Server $server, bool $fromUI): bool
|
|
||||||
{
|
|
||||||
$proxyContainerName = 'coolify-proxy';
|
|
||||||
$status = getContainerStatus($server, $proxyContainerName);
|
|
||||||
|
|
||||||
if ($server->proxy->status !== $status) {
|
|
||||||
$server->proxy->set('status', $status);
|
|
||||||
$server->save();
|
|
||||||
ProxyStatusChanged::dispatch($server->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If already running, no need to start
|
|
||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cloudflare tunnel doesn't need proxy
|
return true;
|
||||||
|
} else {
|
||||||
|
$status = getContainerStatus($server, $proxyContainerName);
|
||||||
|
if ($status === 'running') {
|
||||||
|
$server->proxy->set('status', 'running');
|
||||||
|
$server->save();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if ($server->settings->is_cloudflare_tunnel) {
|
if ($server->settings->is_cloudflare_tunnel) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$ip = $server->ip;
|
||||||
|
if ($server->id === 0) {
|
||||||
|
$ip = 'host.docker.internal';
|
||||||
|
}
|
||||||
|
$portsToCheck = ['80', '443'];
|
||||||
|
|
||||||
// Check for port conflicts
|
try {
|
||||||
$portsToCheck = $this->getPortsToCheck($server);
|
if ($server->proxyType() !== ProxyTypes::NONE->value) {
|
||||||
|
$proxyCompose = CheckConfiguration::run($server);
|
||||||
if (empty($portsToCheck)) {
|
if (isset($proxyCompose)) {
|
||||||
|
$yaml = Yaml::parse($proxyCompose);
|
||||||
|
$configPorts = [];
|
||||||
|
if ($server->proxyType() === ProxyTypes::TRAEFIK->value) {
|
||||||
|
$ports = data_get($yaml, 'services.traefik.ports');
|
||||||
|
} elseif ($server->proxyType() === ProxyTypes::CADDY->value) {
|
||||||
|
$ports = data_get($yaml, 'services.caddy.ports');
|
||||||
|
}
|
||||||
|
if (isset($ports)) {
|
||||||
|
foreach ($ports as $port) {
|
||||||
|
$configPorts[] = str($port)->before(':')->value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Combine default ports with config ports
|
||||||
|
$portsToCheck = array_merge($portsToCheck, $configPorts);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$portsToCheck = [];
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error checking proxy: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
if (count($portsToCheck) === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$portsToCheck = array_values(array_unique($portsToCheck));
|
||||||
$this->validatePortsAvailable($server, $portsToCheck, $proxyContainerName, $fromUI);
|
// Check port conflicts in parallel
|
||||||
|
$conflicts = $this->checkPortConflictsInParallel($server, $portsToCheck, $proxyContainerName);
|
||||||
|
foreach ($conflicts as $port => $conflict) {
|
||||||
|
if ($conflict) {
|
||||||
|
if ($fromUI) {
|
||||||
|
throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br><br>Docs: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/discord'>https://coolify.io/discord</a>");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get list of ports that need to be available for the proxy
|
|
||||||
*/
|
|
||||||
private function getPortsToCheck(Server $server): array
|
|
||||||
{
|
|
||||||
$defaultPorts = ['80', '443'];
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($server->proxyType() === ProxyTypes::NONE->value) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$proxyCompose = CheckConfiguration::run($server);
|
|
||||||
if (! $proxyCompose) {
|
|
||||||
return $defaultPorts;
|
|
||||||
}
|
|
||||||
|
|
||||||
$yaml = Yaml::parse($proxyCompose);
|
|
||||||
$ports = $this->extractPortsFromCompose($yaml, $server->proxyType());
|
|
||||||
|
|
||||||
return ! empty($ports) ? $ports : $defaultPorts;
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('Error checking proxy configuration: '.$e->getMessage());
|
|
||||||
|
|
||||||
return $defaultPorts;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract ports from docker-compose configuration
|
* Check multiple ports for conflicts in parallel
|
||||||
|
* Returns an array with port => conflict_status mapping
|
||||||
*/
|
*/
|
||||||
private function extractPortsFromCompose(array $yaml, string $proxyType): array
|
private function checkPortConflictsInParallel(Server $server, array $ports, string $proxyContainerName): array
|
||||||
{
|
|
||||||
$ports = [];
|
|
||||||
$servicePorts = null;
|
|
||||||
|
|
||||||
if ($proxyType === ProxyTypes::TRAEFIK->value) {
|
|
||||||
$servicePorts = data_get($yaml, 'services.traefik.ports');
|
|
||||||
} elseif ($proxyType === ProxyTypes::CADDY->value) {
|
|
||||||
$servicePorts = data_get($yaml, 'services.caddy.ports');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($servicePorts) {
|
|
||||||
foreach ($servicePorts as $port) {
|
|
||||||
$ports[] = str($port)->before(':')->value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_unique($ports);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that required ports are available (checked in parallel)
|
|
||||||
*/
|
|
||||||
private function validatePortsAvailable(Server $server, array $ports, string $proxyContainerName, bool $fromUI): void
|
|
||||||
{
|
{
|
||||||
if (empty($ports)) {
|
if (empty($ports)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
\Log::info('Checking ports in parallel: '.implode(', ', $ports));
|
|
||||||
|
|
||||||
// Check all ports in parallel
|
|
||||||
$conflicts = $this->checkPortsInParallel($server, $ports, $proxyContainerName);
|
|
||||||
|
|
||||||
// Handle any conflicts found
|
|
||||||
foreach ($conflicts as $port) {
|
|
||||||
$message = "Port $port is in use";
|
|
||||||
if ($fromUI) {
|
|
||||||
$message = "Port $port is in use.<br>You must stop the process using this port.<br><br>".
|
|
||||||
"Docs: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>".
|
|
||||||
"Discord: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/discord'>https://coolify.io/discord</a>";
|
|
||||||
}
|
|
||||||
throw new \Exception($message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check multiple ports for conflicts in parallel using concurrent processes
|
|
||||||
*/
|
|
||||||
private function checkPortsInParallel(Server $server, array $ports, string $proxyContainerName): array
|
|
||||||
{
|
|
||||||
$conflicts = [];
|
|
||||||
|
|
||||||
// First, quickly filter out ports used by our own proxy
|
|
||||||
$ourProxyPorts = $this->getOurProxyPorts($server, $proxyContainerName);
|
|
||||||
$portsToCheck = array_diff($ports, $ourProxyPorts);
|
|
||||||
|
|
||||||
if (empty($portsToCheck)) {
|
|
||||||
\Log::info('All ports are used by our proxy, no conflicts to check');
|
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a single optimized command to check all ports at once
|
|
||||||
$command = $this->buildBatchPortCheckCommand($portsToCheck, $server);
|
|
||||||
if (! $command) {
|
|
||||||
\Log::warning('No suitable port checking method available, falling back to sequential');
|
|
||||||
|
|
||||||
return $this->checkPortsSequentially($server, $portsToCheck, $proxyContainerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$output = instant_remote_process([$command], $server, true);
|
// Build concurrent port check commands
|
||||||
$results = $this->parseBatchPortCheckOutput($output, $portsToCheck);
|
$results = Process::concurrently(function ($pool) use ($server, $ports, $proxyContainerName) {
|
||||||
|
|
||||||
foreach ($results as $port => $isConflict) {
|
|
||||||
if ($isConflict) {
|
|
||||||
$conflicts[] = $port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
\Log::warning('Batch port check failed, falling back to sequential: '.$e->getMessage());
|
|
||||||
|
|
||||||
return $this->checkPortsSequentially($server, $portsToCheck, $proxyContainerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $conflicts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get ports currently used by our proxy container
|
|
||||||
*/
|
|
||||||
private function getOurProxyPorts(Server $server, string $proxyContainerName): array
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$getProxyContainerId = "docker ps -a --filter name=$proxyContainerName --format '{{.ID}}'";
|
|
||||||
$containerId = trim(instant_remote_process([$getProxyContainerId], $server));
|
|
||||||
|
|
||||||
if (empty($containerId)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$getPortsCommand = "docker inspect $containerId --format '{{json .NetworkSettings.Ports}}' 2>/dev/null || echo '{}'";
|
|
||||||
$portsJson = instant_remote_process([$getPortsCommand], $server, true);
|
|
||||||
$portsData = json_decode($portsJson, true);
|
|
||||||
|
|
||||||
$ports = [];
|
|
||||||
if (is_array($portsData)) {
|
|
||||||
foreach (array_keys($portsData) as $portSpec) {
|
|
||||||
if (preg_match('/^(\d+)\/tcp$/', $portSpec, $matches)) {
|
|
||||||
$ports[] = $matches[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ports;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
\Log::warning('Failed to get proxy ports: '.$e->getMessage());
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a single command to check multiple ports efficiently
|
|
||||||
*/
|
|
||||||
private function buildBatchPortCheckCommand(array $ports, Server $server): ?string
|
|
||||||
{
|
|
||||||
$portList = implode('|', $ports);
|
|
||||||
|
|
||||||
// Try different commands in order of preference
|
|
||||||
$commands = [
|
|
||||||
// ss - fastest and most reliable
|
|
||||||
[
|
|
||||||
'check' => 'command -v ss >/dev/null 2>&1',
|
|
||||||
'exec' => "ss -Htuln state listening | grep -E ':($portList) ' | awk '{print \$5}' | cut -d: -f2 | sort -u",
|
|
||||||
],
|
|
||||||
// netstat - widely available
|
|
||||||
[
|
|
||||||
'check' => 'command -v netstat >/dev/null 2>&1',
|
|
||||||
'exec' => "netstat -tuln | grep -E ':($portList) ' | grep LISTEN | awk '{print \$4}' | cut -d: -f2 | sort -u",
|
|
||||||
],
|
|
||||||
// lsof - last resort
|
|
||||||
[
|
|
||||||
'check' => 'command -v lsof >/dev/null 2>&1',
|
|
||||||
'exec' => "lsof -iTCP -sTCP:LISTEN -P -n | grep -E ':($portList) ' | awk '{print \$9}' | cut -d: -f2 | sort -u",
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($commands as $cmd) {
|
|
||||||
try {
|
|
||||||
instant_remote_process([$cmd['check']], $server);
|
|
||||||
|
|
||||||
return $cmd['exec'];
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse output from batch port check command
|
|
||||||
*/
|
|
||||||
private function parseBatchPortCheckOutput(string $output, array $portsToCheck): array
|
|
||||||
{
|
|
||||||
$results = [];
|
|
||||||
$usedPorts = array_filter(explode("\n", trim($output)));
|
|
||||||
|
|
||||||
foreach ($portsToCheck as $port) {
|
|
||||||
$results[$port] = in_array($port, $usedPorts);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fallback to sequential checking if parallel fails
|
|
||||||
*/
|
|
||||||
private function checkPortsSequentially(Server $server, array $ports, string $proxyContainerName): array
|
|
||||||
{
|
|
||||||
$conflicts = [];
|
|
||||||
|
|
||||||
foreach ($ports as $port) {
|
foreach ($ports as $port) {
|
||||||
try {
|
$commands = $this->buildPortCheckCommands($server, $port, $proxyContainerName);
|
||||||
if ($this->isPortConflict($server, $port, $proxyContainerName)) {
|
$pool->command($commands['ssh_command'])->timeout(10);
|
||||||
$conflicts[] = $port;
|
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
});
|
||||||
\Log::warning("Sequential port check failed for port $port: ".$e->getMessage());
|
|
||||||
// Continue checking other ports
|
// Process results
|
||||||
|
$conflicts = [];
|
||||||
|
|
||||||
|
foreach ($ports as $index => $port) {
|
||||||
|
$result = $results[$index] ?? null;
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
$conflicts[$port] = $this->parsePortCheckResult($result, $port, $proxyContainerName);
|
||||||
|
} else {
|
||||||
|
// If process failed, assume no conflict to avoid false positives
|
||||||
|
$conflicts[$port] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $conflicts;
|
return $conflicts;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('Parallel port checking failed: '.$e->getMessage().'. Falling back to sequential checking.');
|
||||||
|
|
||||||
|
// Fallback to sequential checking if parallel fails
|
||||||
|
$conflicts = [];
|
||||||
|
foreach ($ports as $port) {
|
||||||
|
$conflicts[$port] = $this->isPortConflict($server, $port, $proxyContainerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $conflicts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the SSH command for checking a specific port
|
||||||
|
*/
|
||||||
|
private function buildPortCheckCommands(Server $server, string $port, string $proxyContainerName): array
|
||||||
|
{
|
||||||
|
// First check if our own proxy is using this port (which is fine)
|
||||||
|
$getProxyContainerId = "docker ps -a --filter name=$proxyContainerName --format '{{.ID}}'";
|
||||||
|
$checkProxyPortScript = "
|
||||||
|
CONTAINER_ID=\$($getProxyContainerId);
|
||||||
|
if [ ! -z \"\$CONTAINER_ID\" ]; then
|
||||||
|
if docker inspect \$CONTAINER_ID --format '{{json .NetworkSettings.Ports}}' | grep -q '\"$port/tcp\"'; then
|
||||||
|
echo 'proxy_using_port';
|
||||||
|
exit 0;
|
||||||
|
fi;
|
||||||
|
fi;
|
||||||
|
";
|
||||||
|
|
||||||
|
// Command sets for different ways to check ports, ordered by preference
|
||||||
|
$portCheckScript = "
|
||||||
|
$checkProxyPortScript
|
||||||
|
|
||||||
|
# Try ss command first
|
||||||
|
if command -v ss >/dev/null 2>&1; then
|
||||||
|
ss_output=\$(ss -Htuln state listening sport = :$port 2>/dev/null);
|
||||||
|
if [ -z \"\$ss_output\" ]; then
|
||||||
|
echo 'port_free';
|
||||||
|
exit 0;
|
||||||
|
fi;
|
||||||
|
count=\$(echo \"\$ss_output\" | grep -c ':$port ');
|
||||||
|
if [ \$count -eq 0 ]; then
|
||||||
|
echo 'port_free';
|
||||||
|
exit 0;
|
||||||
|
fi;
|
||||||
|
# Check for dual-stack or docker processes
|
||||||
|
if [ \$count -le 2 ] && (echo \"\$ss_output\" | grep -q 'docker\\|coolify'); then
|
||||||
|
echo 'port_free';
|
||||||
|
exit 0;
|
||||||
|
fi;
|
||||||
|
echo \"port_conflict|\$ss_output\";
|
||||||
|
exit 0;
|
||||||
|
fi;
|
||||||
|
|
||||||
|
# Try netstat as fallback
|
||||||
|
if command -v netstat >/dev/null 2>&1; then
|
||||||
|
netstat_output=\$(netstat -tuln 2>/dev/null | grep ':$port ');
|
||||||
|
if [ -z \"\$netstat_output\" ]; then
|
||||||
|
echo 'port_free';
|
||||||
|
exit 0;
|
||||||
|
fi;
|
||||||
|
count=\$(echo \"\$netstat_output\" | grep -c 'LISTEN');
|
||||||
|
if [ \$count -eq 0 ]; then
|
||||||
|
echo 'port_free';
|
||||||
|
exit 0;
|
||||||
|
fi;
|
||||||
|
if [ \$count -le 2 ] && (echo \"\$netstat_output\" | grep -q 'docker\\|coolify'); then
|
||||||
|
echo 'port_free';
|
||||||
|
exit 0;
|
||||||
|
fi;
|
||||||
|
echo \"port_conflict|\$netstat_output\";
|
||||||
|
exit 0;
|
||||||
|
fi;
|
||||||
|
|
||||||
|
# Final fallback using nc
|
||||||
|
if nc -z -w1 127.0.0.1 $port >/dev/null 2>&1; then
|
||||||
|
echo 'port_conflict|nc_detected';
|
||||||
|
else
|
||||||
|
echo 'port_free';
|
||||||
|
fi;
|
||||||
|
";
|
||||||
|
|
||||||
|
$sshCommand = \App\Helpers\SshMultiplexingHelper::generateSshCommand($server, $portCheckScript);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ssh_command' => $sshCommand,
|
||||||
|
'script' => $portCheckScript,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the result from port check command
|
||||||
|
*/
|
||||||
|
private function parsePortCheckResult($processResult, string $port, string $proxyContainerName): bool
|
||||||
|
{
|
||||||
|
$exitCode = $processResult->exitCode();
|
||||||
|
$output = trim($processResult->output());
|
||||||
|
$errorOutput = trim($processResult->errorOutput());
|
||||||
|
|
||||||
|
if ($exitCode !== 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($output === 'proxy_using_port' || $output === 'port_free') {
|
||||||
|
return false; // No conflict
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($output, 'port_conflict|')) {
|
||||||
|
$details = substr($output, strlen('port_conflict|'));
|
||||||
|
|
||||||
|
// Additional logic to detect dual-stack scenarios
|
||||||
|
if ($details !== 'nc_detected') {
|
||||||
|
// Check for dual-stack scenario - typically 1-2 listeners (IPv4+IPv6)
|
||||||
|
$lines = explode("\n", $details);
|
||||||
|
if (count($lines) <= 2) {
|
||||||
|
// Look for IPv4 and IPv6 in the listing
|
||||||
|
if ((strpos($details, '0.0.0.0:'.$port) !== false && strpos($details, ':::'.$port) !== false) ||
|
||||||
|
(strpos($details, '*:'.$port) !== false && preg_match('/\*:'.$port.'.*IPv[46]/', $details))) {
|
||||||
|
|
||||||
|
return false; // This is just a normal dual-stack setup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // Real port conflict
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -363,168 +284,134 @@ class CheckProxy
|
|||||||
*/
|
*/
|
||||||
private function isPortConflict(Server $server, string $port, string $proxyContainerName): bool
|
private function isPortConflict(Server $server, string $port, string $proxyContainerName): bool
|
||||||
{
|
{
|
||||||
// Check if our own proxy is using this port (which is fine)
|
// First check if our own proxy is using this port (which is fine)
|
||||||
if ($this->isOurProxyUsingPort($server, $port, $proxyContainerName)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try different methods to detect port conflicts
|
|
||||||
$portCheckMethods = [
|
|
||||||
'checkWithSs',
|
|
||||||
'checkWithNetstat',
|
|
||||||
'checkWithLsof',
|
|
||||||
'checkWithNetcat',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($portCheckMethods as $method) {
|
|
||||||
try {
|
|
||||||
$result = $this->$method($server, $port);
|
|
||||||
if ($result !== null) {
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
continue; // Try next method
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all methods fail, assume port is free to avoid false positives
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if our own proxy container is using the port
|
|
||||||
*/
|
|
||||||
private function isOurProxyUsingPort(Server $server, string $port, string $proxyContainerName): bool
|
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
$getProxyContainerId = "docker ps -a --filter name=$proxyContainerName --format '{{.ID}}'";
|
$getProxyContainerId = "docker ps -a --filter name=$proxyContainerName --format '{{.ID}}'";
|
||||||
$containerId = trim(instant_remote_process([$getProxyContainerId], $server));
|
$containerId = trim(instant_remote_process([$getProxyContainerId], $server));
|
||||||
|
|
||||||
if (empty($containerId)) {
|
if (! empty($containerId)) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$checkProxyPort = "docker inspect $containerId --format '{{json .NetworkSettings.Ports}}' | grep '\"$port/tcp\"'";
|
$checkProxyPort = "docker inspect $containerId --format '{{json .NetworkSettings.Ports}}' | grep '\"$port/tcp\"'";
|
||||||
|
try {
|
||||||
instant_remote_process([$checkProxyPort], $server);
|
instant_remote_process([$checkProxyPort], $server);
|
||||||
|
|
||||||
return true; // Our proxy is using the port
|
// Our proxy is using the port, which is fine
|
||||||
|
return false;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
// Our container exists but not using this port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Container not found or error checking, continue with regular checks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command sets for different ways to check ports, ordered by preference
|
||||||
|
$commandSets = [
|
||||||
|
// Set 1: Use ss to check listener counts by protocol stack
|
||||||
|
[
|
||||||
|
'available' => 'command -v ss >/dev/null 2>&1',
|
||||||
|
'check' => [
|
||||||
|
// Get listening process details
|
||||||
|
"ss_output=\$(ss -Htuln state listening sport = :$port 2>/dev/null) && echo \"\$ss_output\"",
|
||||||
|
// Count IPv4 listeners
|
||||||
|
"echo \"\$ss_output\" | grep -c ':$port '",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// Set 2: Use netstat as alternative to ss
|
||||||
|
[
|
||||||
|
'available' => 'command -v netstat >/dev/null 2>&1',
|
||||||
|
'check' => [
|
||||||
|
// Get listening process details
|
||||||
|
"netstat_output=\$(netstat -tuln 2>/dev/null) && echo \"\$netstat_output\" | grep ':$port '",
|
||||||
|
// Count listeners
|
||||||
|
"echo \"\$netstat_output\" | grep ':$port ' | grep -c 'LISTEN'",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// Set 3: Use lsof as last resort
|
||||||
|
[
|
||||||
|
'available' => 'command -v lsof >/dev/null 2>&1',
|
||||||
|
'check' => [
|
||||||
|
// Get process using the port
|
||||||
|
"lsof -i :$port -P -n | grep 'LISTEN'",
|
||||||
|
// Count listeners
|
||||||
|
"lsof -i :$port -P -n | grep 'LISTEN' | wc -l",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Try each command set until we find one available
|
||||||
|
foreach ($commandSets as $set) {
|
||||||
|
try {
|
||||||
|
// Check if the command is available
|
||||||
|
instant_remote_process([$set['available']], $server);
|
||||||
|
|
||||||
|
// Run the actual check commands
|
||||||
|
$output = instant_remote_process($set['check'], $server, true);
|
||||||
|
// Parse the output lines
|
||||||
|
$lines = explode("\n", trim($output));
|
||||||
|
// Get the detailed output and listener count
|
||||||
|
$details = trim(implode("\n", array_slice($lines, 0, -1)));
|
||||||
|
$count = intval(trim($lines[count($lines) - 1] ?? '0'));
|
||||||
|
// If no listeners or empty result, port is free
|
||||||
|
if ($count == 0 || empty($details)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to detect if this is our coolify-proxy
|
||||||
|
if (strpos($details, 'docker') !== false || strpos($details, $proxyContainerName) !== false) {
|
||||||
|
// It's likely our docker or proxy, which is fine
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Check for dual-stack scenario - typically 1-2 listeners (IPv4+IPv6)
|
||||||
* Check port usage with ss command
|
// If exactly 2 listeners and both have same port, likely dual-stack
|
||||||
*/
|
if ($count <= 2) {
|
||||||
private function checkWithSs(Server $server, string $port): ?bool
|
// Check if it looks like a standard dual-stack setup
|
||||||
{
|
$isDualStack = false;
|
||||||
$commands = [
|
|
||||||
'command -v ss >/dev/null 2>&1',
|
|
||||||
"ss_output=\$(ss -Htuln state listening sport = :$port 2>/dev/null) && echo \"\$ss_output\"",
|
|
||||||
"echo \"\$ss_output\" | grep -c ':$port '",
|
|
||||||
];
|
|
||||||
|
|
||||||
$output = instant_remote_process($commands, $server, true);
|
// Look for IPv4 and IPv6 in the listing (ss output format)
|
||||||
|
if (preg_match('/LISTEN.*:'.$port.'\s/', $details) &&
|
||||||
return $this->parsePortCheckOutput($output, $port);
|
(preg_match('/\*:'.$port.'\s/', $details) ||
|
||||||
|
preg_match('/:::'.$port.'\s/', $details))) {
|
||||||
|
$isDualStack = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// For netstat format
|
||||||
* Check port usage with netstat command
|
if (strpos($details, '0.0.0.0:'.$port) !== false &&
|
||||||
*/
|
strpos($details, ':::'.$port) !== false) {
|
||||||
private function checkWithNetstat(Server $server, string $port): ?bool
|
$isDualStack = true;
|
||||||
{
|
|
||||||
$commands = [
|
|
||||||
'command -v netstat >/dev/null 2>&1',
|
|
||||||
"netstat_output=\$(netstat -tuln 2>/dev/null) && echo \"\$netstat_output\" | grep ':$port '",
|
|
||||||
"echo \"\$netstat_output\" | grep ':$port ' | grep -c 'LISTEN'",
|
|
||||||
];
|
|
||||||
|
|
||||||
$output = instant_remote_process($commands, $server, true);
|
|
||||||
|
|
||||||
return $this->parsePortCheckOutput($output, $port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// For lsof format (IPv4 and IPv6)
|
||||||
* Check port usage with lsof command
|
if (strpos($details, '*:'.$port) !== false &&
|
||||||
*/
|
preg_match('/\*:'.$port.'.*IPv4/', $details) &&
|
||||||
private function checkWithLsof(Server $server, string $port): ?bool
|
preg_match('/\*:'.$port.'.*IPv6/', $details)) {
|
||||||
{
|
$isDualStack = true;
|
||||||
$commands = [
|
|
||||||
'command -v lsof >/dev/null 2>&1',
|
|
||||||
"lsof -i :$port -P -n | grep 'LISTEN'",
|
|
||||||
"lsof -i :$port -P -n | grep 'LISTEN' | wc -l",
|
|
||||||
];
|
|
||||||
|
|
||||||
$output = instant_remote_process($commands, $server, true);
|
|
||||||
|
|
||||||
return $this->parsePortCheckOutput($output, $port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if ($isDualStack) {
|
||||||
* Check port usage with netcat as fallback
|
return false; // This is just a normal dual-stack setup
|
||||||
*/
|
}
|
||||||
private function checkWithNetcat(Server $server, string $port): ?bool
|
}
|
||||||
{
|
|
||||||
|
// If we get here, it's likely a real port conflict
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// This command set failed, try the next one
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to simpler check if all above methods fail
|
||||||
|
try {
|
||||||
|
// Just try to bind to the port directly to see if it's available
|
||||||
$checkCommand = "nc -z -w1 127.0.0.1 $port >/dev/null 2>&1 && echo 'in-use' || echo 'free'";
|
$checkCommand = "nc -z -w1 127.0.0.1 $port >/dev/null 2>&1 && echo 'in-use' || echo 'free'";
|
||||||
$result = instant_remote_process([$checkCommand], $server, true);
|
$result = instant_remote_process([$checkCommand], $server, true);
|
||||||
|
|
||||||
return trim($result) === 'in-use';
|
return trim($result) === 'in-use';
|
||||||
}
|
} catch (\Throwable $e) {
|
||||||
|
// If everything fails, assume the port is free to avoid false positives
|
||||||
/**
|
|
||||||
* Parse output from port checking commands
|
|
||||||
*/
|
|
||||||
private function parsePortCheckOutput(string $output, string $port): ?bool
|
|
||||||
{
|
|
||||||
$lines = explode("\n", trim($output));
|
|
||||||
$details = trim($lines[0] ?? '');
|
|
||||||
$count = intval(trim($lines[1] ?? '0'));
|
|
||||||
|
|
||||||
// No listeners found
|
|
||||||
if ($count === 0 || empty($details)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's likely our docker/proxy process
|
|
||||||
if (strpos($details, 'docker') !== false || strpos($details, 'coolify-proxy') !== false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle dual-stack scenarios (1-2 listeners for IPv4+IPv6)
|
|
||||||
if ($count <= 2 && $this->isDualStackSetup($details, $port)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Real port conflict detected
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect if this is a normal dual-stack setup (IPv4 + IPv6)
|
|
||||||
*/
|
|
||||||
private function isDualStackSetup(string $details, string $port): bool
|
|
||||||
{
|
|
||||||
$patterns = [
|
|
||||||
// ss output format
|
|
||||||
'/LISTEN.*:'.$port.'\s/',
|
|
||||||
'/\*:'.$port.'\s/',
|
|
||||||
'/:::'.$port.'\s/',
|
|
||||||
// netstat format
|
|
||||||
'/0\.0\.0\.0:'.$port.'/',
|
|
||||||
'/:::'.$port.'/',
|
|
||||||
// lsof format
|
|
||||||
'/\*:'.$port.'.*IPv[46]/',
|
|
||||||
];
|
|
||||||
|
|
||||||
$matches = 0;
|
|
||||||
foreach ($patterns as $pattern) {
|
|
||||||
if (preg_match($pattern, $details)) {
|
|
||||||
$matches++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we see patterns indicating both IPv4 and IPv6, it's likely dual-stack
|
|
||||||
return $matches >= 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Services\ProxyDashboardCacheService;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class SaveConfiguration
|
class SaveConfiguration
|
||||||
@@ -21,9 +20,6 @@ class SaveConfiguration
|
|||||||
$server->proxy->last_saved_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_saved_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
|
|
||||||
// Clear Traefik dashboard cache when configuration is saved
|
|
||||||
ProxyDashboardCacheService::clearCache($server);
|
|
||||||
|
|
||||||
return instant_remote_process([
|
return instant_remote_process([
|
||||||
"mkdir -p $proxy_path",
|
"mkdir -p $proxy_path",
|
||||||
"echo '$docker_compose_yml_base64' | base64 -d | tee $proxy_path/docker-compose.yml > /dev/null",
|
"echo '$docker_compose_yml_base64' | base64 -d | tee $proxy_path/docker-compose.yml > /dev/null",
|
||||||
|
@@ -4,8 +4,8 @@ namespace App\Actions\Proxy;
|
|||||||
|
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use App\Events\ProxyStatusChanged;
|
use App\Events\ProxyStatusChanged;
|
||||||
|
use App\Events\ProxyStatusChangedUI;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Services\ProxyDashboardCacheService;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
@@ -30,9 +30,6 @@ class StartProxy
|
|||||||
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
|
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
|
||||||
$server->save();
|
$server->save();
|
||||||
|
|
||||||
// Clear Traefik dashboard cache when proxy configuration changes
|
|
||||||
ProxyDashboardCacheService::clearCache($server);
|
|
||||||
|
|
||||||
if ($server->isSwarmManager()) {
|
if ($server->isSwarmManager()) {
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
"mkdir -p $proxy_path/dynamic",
|
"mkdir -p $proxy_path/dynamic",
|
||||||
@@ -62,11 +59,14 @@ class StartProxy
|
|||||||
" echo 'Successfully stopped and removed existing coolify-proxy.'",
|
" echo 'Successfully stopped and removed existing coolify-proxy.'",
|
||||||
'fi',
|
'fi',
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
'docker compose up -d --wait',
|
'docker compose up -d --wait --remove-orphans',
|
||||||
"echo 'Successfully started coolify-proxy.'",
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||||
}
|
}
|
||||||
|
$server->proxy->set('status', 'starting');
|
||||||
|
$server->save();
|
||||||
|
ProxyStatusChangedUI::dispatch($server->team_id);
|
||||||
|
|
||||||
if ($async) {
|
if ($async) {
|
||||||
return remote_process($commands, $server, callEventOnFinish: 'ProxyStatusChanged', callEventData: $server->id);
|
return remote_process($commands, $server, callEventOnFinish: 'ProxyStatusChanged', callEventData: $server->id);
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Events\ProxyStatusChanged;
|
use App\Events\ProxyStatusChanged;
|
||||||
|
use App\Events\ProxyStatusChangedUI;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Services\ProxyDashboardCacheService;
|
use App\Services\ProxyDashboardCacheService;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -15,6 +16,9 @@ class StopProxy
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$containerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
$containerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
||||||
|
$server->proxy->status = 'stopping';
|
||||||
|
$server->save();
|
||||||
|
ProxyStatusChangedUI::dispatch($server->team_id);
|
||||||
|
|
||||||
instant_remote_process(command: [
|
instant_remote_process(command: [
|
||||||
"docker stop --time=$timeout $containerName",
|
"docker stop --time=$timeout $containerName",
|
||||||
@@ -24,12 +28,10 @@ class StopProxy
|
|||||||
$server->proxy->force_stop = $forceStop;
|
$server->proxy->force_stop = $forceStop;
|
||||||
$server->proxy->status = 'exited';
|
$server->proxy->status = 'exited';
|
||||||
$server->save();
|
$server->save();
|
||||||
|
|
||||||
// Clear Traefik dashboard cache when proxy stops
|
|
||||||
ProxyDashboardCacheService::clearCache($server);
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
} finally {
|
} finally {
|
||||||
|
ProxyDashboardCacheService::clearCache($server);
|
||||||
ProxyStatusChanged::dispatch($server->id);
|
ProxyStatusChanged::dispatch($server->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Actions\Proxy\CheckProxy;
|
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Actions\Proxy\StopProxy;
|
use App\Actions\Proxy\StopProxy;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Services\ProxyDashboardCacheService;
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@@ -40,10 +38,6 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
StartProxy::run($this->server, force: true);
|
StartProxy::run($this->server, force: true);
|
||||||
|
|
||||||
// Clear Traefik dashboard cache after proxy restart
|
|
||||||
ProxyDashboardCacheService::clearCache($this->server);
|
|
||||||
|
|
||||||
// CheckProxy::run($this->server, true);
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,10 @@ class ProxyStatusChangedNotification implements ShouldQueueAfterCommit
|
|||||||
public function handle(ProxyStatusChanged $event)
|
public function handle(ProxyStatusChanged $event)
|
||||||
{
|
{
|
||||||
$serverId = $event->data;
|
$serverId = $event->data;
|
||||||
$server = Server::find($serverId);
|
if (is_null($serverId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = Server::where('id', $serverId)->first();
|
||||||
if (is_null($server)) {
|
if (is_null($server)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -23,12 +26,17 @@ class ProxyStatusChangedNotification implements ShouldQueueAfterCommit
|
|||||||
$server->proxy->set('status', $status);
|
$server->proxy->set('status', $status);
|
||||||
$server->save();
|
$server->save();
|
||||||
|
|
||||||
|
ProxyStatusChangedUI::dispatch($server->team_id);
|
||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
$server->setupDefaultRedirect();
|
$server->setupDefaultRedirect();
|
||||||
$server->setupDynamicProxyConfiguration();
|
$server->setupDynamicProxyConfiguration();
|
||||||
$server->proxy->force_stop = false;
|
$server->proxy->force_stop = false;
|
||||||
$server->save();
|
$server->save();
|
||||||
}
|
}
|
||||||
ProxyStatusChangedUI::dispatch($server->team_id);
|
if ($status === 'created') {
|
||||||
|
instant_remote_process([
|
||||||
|
'docker rm -f coolify-proxy',
|
||||||
|
], $server);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,10 +20,10 @@ class ActivityMonitor extends Component
|
|||||||
|
|
||||||
public bool $fullHeight = false;
|
public bool $fullHeight = false;
|
||||||
|
|
||||||
public bool $showWaiting = false;
|
|
||||||
|
|
||||||
public $activity;
|
public $activity;
|
||||||
|
|
||||||
|
public bool $showWaiting = true;
|
||||||
|
|
||||||
public static $eventDispatched = false;
|
public static $eventDispatched = false;
|
||||||
|
|
||||||
protected $listeners = ['activityMonitor' => 'newMonitorActivity'];
|
protected $listeners = ['activityMonitor' => 'newMonitorActivity'];
|
||||||
|
@@ -22,11 +22,14 @@ class Navbar extends Component
|
|||||||
|
|
||||||
public ?string $serverIp = null;
|
public ?string $serverIp = null;
|
||||||
|
|
||||||
|
public ?string $proxyStatus = 'unknown';
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$teamId = auth()->user()->currentTeam()->id;
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
'refreshServerShow' => '$refresh',
|
||||||
"echo-private:team.{$teamId},ProxyStatusChangedUI" => 'showNotification',
|
"echo-private:team.{$teamId},ProxyStatusChangedUI" => 'showNotification',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -36,13 +39,16 @@ class Navbar extends Component
|
|||||||
$this->server = $server;
|
$this->server = $server;
|
||||||
$this->currentRoute = request()->route()->getName();
|
$this->currentRoute = request()->route()->getName();
|
||||||
$this->serverIp = $this->server->id === 0 ? base_ip() : $this->server->ip;
|
$this->serverIp = $this->server->id === 0 ? base_ip() : $this->server->ip;
|
||||||
|
$this->proxyStatus = $this->server->proxy->status ?? 'unknown';
|
||||||
$this->loadProxyConfiguration();
|
$this->loadProxyConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadProxyConfiguration()
|
public function loadProxyConfiguration()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->traefikDashboardAvailable = ProxyDashboardCacheService::isTraefikDashboardAvailable($this->server);
|
if ($this->proxyStatus === 'running') {
|
||||||
|
$this->traefikDashboardAvailable = ProxyDashboardCacheService::isTraefikDashboardAvailableFromCache($this->server);
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -51,8 +57,6 @@ class Navbar extends Component
|
|||||||
public function restart()
|
public function restart()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Clear cache before restarting proxy
|
|
||||||
ProxyDashboardCacheService::clearCache($this->server);
|
|
||||||
RestartProxyJob::dispatch($this->server);
|
RestartProxyJob::dispatch($this->server);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -97,37 +101,42 @@ class Navbar extends Component
|
|||||||
try {
|
try {
|
||||||
$this->isChecking = true;
|
$this->isChecking = true;
|
||||||
CheckProxy::run($this->server, true);
|
CheckProxy::run($this->server, true);
|
||||||
$this->showNotification();
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
$this->isChecking = false;
|
$this->isChecking = false;
|
||||||
|
$this->showNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showNotification()
|
public function showNotification()
|
||||||
{
|
{
|
||||||
$status = $this->server->proxy->status ?? 'unknown';
|
$this->proxyStatus = $this->server->proxy->status ?? 'unknown';
|
||||||
$forceStop = $this->server->proxy->force_stop ?? false;
|
$forceStop = $this->server->proxy->force_stop ?? false;
|
||||||
|
|
||||||
switch ($status) {
|
switch ($this->proxyStatus) {
|
||||||
case 'running':
|
case 'running':
|
||||||
$this->dispatch('success', 'Proxy is running.');
|
// $this->dispatch('success', 'Proxy is running.');
|
||||||
|
$this->loadProxyConfiguration();
|
||||||
break;
|
break;
|
||||||
case 'restarting':
|
case 'restarting':
|
||||||
$this->dispatch('info', 'Initiating proxy restart.');
|
$this->dispatch('info', 'Initiating proxy restart.');
|
||||||
break;
|
break;
|
||||||
case 'exited':
|
// case 'exited':
|
||||||
if ($forceStop) {
|
// if (! $forceStop) {
|
||||||
$this->dispatch('info', 'Proxy is stopped manually.');
|
// $this->dispatch('info', 'Proxy is stopped manually.<br>Starting in a moment.');
|
||||||
} else {
|
// }
|
||||||
$this->dispatch('info', 'Proxy is stopped manually.<br>Starting in a moment.');
|
// break;
|
||||||
}
|
case 'starting':
|
||||||
|
// do nothing
|
||||||
|
break;
|
||||||
|
case 'stopping':
|
||||||
|
// do nothing
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$this->dispatch('warning', 'Proxy is not running.');
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
@@ -5,7 +5,6 @@ namespace App\Livewire\Server;
|
|||||||
use App\Actions\Proxy\CheckConfiguration;
|
use App\Actions\Proxy\CheckConfiguration;
|
||||||
use App\Actions\Proxy\SaveConfiguration;
|
use App\Actions\Proxy\SaveConfiguration;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Services\ProxyDashboardCacheService;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Proxy extends Component
|
class Proxy extends Component
|
||||||
@@ -43,9 +42,6 @@ class Proxy extends Component
|
|||||||
$this->server->proxy = null;
|
$this->server->proxy = null;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
|
||||||
// Clear Traefik dashboard cache when proxy type changes
|
|
||||||
ProxyDashboardCacheService::clearCache($this->server);
|
|
||||||
|
|
||||||
$this->dispatch('reloadWindow');
|
$this->dispatch('reloadWindow');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,9 +51,6 @@ class Proxy extends Component
|
|||||||
$this->server->changeProxy($proxy_type, async: false);
|
$this->server->changeProxy($proxy_type, async: false);
|
||||||
$this->selectedProxy = $this->server->proxy->type;
|
$this->selectedProxy = $this->server->proxy->type;
|
||||||
|
|
||||||
// Clear Traefik dashboard cache when proxy type is selected
|
|
||||||
ProxyDashboardCacheService::clearCache($this->server);
|
|
||||||
|
|
||||||
$this->dispatch('reloadWindow');
|
$this->dispatch('reloadWindow');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
@@ -184,7 +184,6 @@ class Show extends Component
|
|||||||
public function refresh()
|
public function refresh()
|
||||||
{
|
{
|
||||||
$this->syncData();
|
$this->syncData();
|
||||||
$this->dispatch('$refresh');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateServer($install = true)
|
public function validateServer($install = true)
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Server;
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -25,8 +27,6 @@ class ValidateAndInstall extends Component
|
|||||||
|
|
||||||
public $docker_version = null;
|
public $docker_version = null;
|
||||||
|
|
||||||
public $proxy_started = false;
|
|
||||||
|
|
||||||
public $error = null;
|
public $error = null;
|
||||||
|
|
||||||
public bool $ask = false;
|
public bool $ask = false;
|
||||||
@@ -47,7 +47,6 @@ class ValidateAndInstall extends Component
|
|||||||
$this->docker_installed = null;
|
$this->docker_installed = null;
|
||||||
$this->docker_version = null;
|
$this->docker_version = null;
|
||||||
$this->docker_compose_installed = null;
|
$this->docker_compose_installed = null;
|
||||||
$this->proxy_started = null;
|
|
||||||
$this->error = null;
|
$this->error = null;
|
||||||
$this->number_of_tries = $data;
|
$this->number_of_tries = $data;
|
||||||
if (! $this->ask) {
|
if (! $this->ask) {
|
||||||
@@ -135,7 +134,12 @@ class ValidateAndInstall extends Component
|
|||||||
if ($this->docker_version) {
|
if ($this->docker_version) {
|
||||||
$this->dispatch('refreshServerShow');
|
$this->dispatch('refreshServerShow');
|
||||||
$this->dispatch('refreshBoardingIndex');
|
$this->dispatch('refreshBoardingIndex');
|
||||||
$this->dispatch('success', 'Server validated.');
|
$this->dispatch('success', 'Server validated, proxy is starting in a moment.');
|
||||||
|
$proxyShouldRun = CheckProxy::run($this->server, true);
|
||||||
|
if (! $proxyShouldRun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartProxy::dispatch($this->server);
|
||||||
} else {
|
} else {
|
||||||
$requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
|
$requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
|
||||||
$this->error = 'Minimum Docker Engine version '.$requiredDockerVersion.' is not instaled. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
$this->error = 'Minimum Docker Engine version '.$requiredDockerVersion.' is not instaled. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
@@ -15,34 +15,25 @@ class ProxyDashboardCacheService
|
|||||||
return "server:{$server->id}:traefik:dashboard_available";
|
return "server:{$server->id}:traefik:dashboard_available";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Traefik dashboard is available from configuration
|
||||||
|
*/
|
||||||
|
public static function isTraefikDashboardAvailableFromConfiguration(Server $server, string $proxy_configuration): void
|
||||||
|
{
|
||||||
|
$cacheKey = static::getCacheKey($server);
|
||||||
|
$dashboardAvailable = str($proxy_configuration)->contains('--api.dashboard=true') &&
|
||||||
|
str($proxy_configuration)->contains('--api.insecure=true');
|
||||||
|
Cache::forever($cacheKey, $dashboardAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if Traefik dashboard is available (from cache or compute)
|
* Check if Traefik dashboard is available (from cache or compute)
|
||||||
*/
|
*/
|
||||||
public static function isTraefikDashboardAvailable(Server $server): bool
|
public static function isTraefikDashboardAvailableFromCache(Server $server): bool
|
||||||
{
|
{
|
||||||
$cacheKey = static::getCacheKey($server);
|
$cacheKey = static::getCacheKey($server);
|
||||||
|
|
||||||
// Try to get from cache first
|
return Cache::get($cacheKey) ?? false;
|
||||||
$cachedValue = Cache::get($cacheKey);
|
|
||||||
|
|
||||||
if ($cachedValue !== null) {
|
|
||||||
return $cachedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not in cache, compute the value
|
|
||||||
try {
|
|
||||||
$proxy_settings = \App\Actions\Proxy\CheckConfiguration::run($server);
|
|
||||||
$dashboardAvailable = str($proxy_settings)->contains('--api.dashboard=true') &&
|
|
||||||
str($proxy_settings)->contains('--api.insecure=true');
|
|
||||||
|
|
||||||
// Cache the result (cache indefinitely until proxy restart)
|
|
||||||
Cache::forever($cacheKey, $dashboardAvailable);
|
|
||||||
|
|
||||||
return $dashboardAvailable;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
// If there's an error checking configuration, default to false and don't cache
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,8 +41,7 @@ class ProxyDashboardCacheService
|
|||||||
*/
|
*/
|
||||||
public static function clearCache(Server $server): void
|
public static function clearCache(Server $server): void
|
||||||
{
|
{
|
||||||
$cacheKey = static::getCacheKey($server);
|
Cache::forget(static::getCacheKey($server));
|
||||||
Cache::forget($cacheKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
<x-loading wire:loading.delay.longer />
|
<x-loading wire:loading.delay.longer />
|
||||||
@endif
|
@endif
|
||||||
<span wire:loading.remove.delay.longer class="flex items-center">
|
<span wire:loading.remove.delay.longer class="flex items-center">
|
||||||
<div class="badge badge-warning "></div>
|
<div class="badge badge-warning"></div>
|
||||||
<div class="pl-2 pr-1 text-xs font-bold tracking-wider dark:text-warning" @if($title) title="{{$title}}" @endif>
|
<div class="pl-2 pr-1 text-xs font-bold tracking-wider dark:text-warning" @if($title) title="{{$title}}" @endif>
|
||||||
@if ($lastDeploymentLink)
|
@if ($lastDeploymentLink)
|
||||||
<a href="{{ $lastDeploymentLink }}" target="_blank" class="underline cursor-pointer">
|
<a href="{{ $lastDeploymentLink }}" target="_blank" class="underline cursor-pointer">
|
||||||
|
@@ -57,7 +57,7 @@
|
|||||||
@else
|
@else
|
||||||
@if ($showWaiting)
|
@if ($showWaiting)
|
||||||
<div class="flex justify-start">
|
<div class="flex justify-start">
|
||||||
<x-loading text="Waiting..." />
|
<x-loading text="Waiting for the process to start..." />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<x-slide-over @startdatabase.window="slideOverOpen = true" closeWithX fullScreen>
|
<x-slide-over @startdatabase.window="slideOverOpen = true" closeWithX fullScreen>
|
||||||
<x-slot:title>Database Startup</x-slot:title>
|
<x-slot:title>Database Startup</x-slot:title>
|
||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
<livewire:activity-monitor header="Logs" showWaiting fullHeight />
|
<livewire:activity-monitor header="Logs" fullHeight />
|
||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<x-slide-over @startservice.window="slideOverOpen = true" closeWithX fullScreen>
|
<x-slide-over @startservice.window="slideOverOpen = true" closeWithX fullScreen>
|
||||||
<x-slot:title>Service Startup</x-slot:title>
|
<x-slot:title>Service Startup</x-slot:title>
|
||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
<livewire:activity-monitor header="Logs" showWaiting fullHeight />
|
<livewire:activity-monitor header="Logs" fullHeight />
|
||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
<h1>{{ $title }}</h1>
|
<h1>{{ $title }}</h1>
|
||||||
|
@@ -87,7 +87,7 @@
|
|||||||
<x-slide-over @automated.window="slideOverOpen = true" fullScreen>
|
<x-slide-over @automated.window="slideOverOpen = true" fullScreen>
|
||||||
<x-slot:title>Cloudflare Tunnel Configuration</x-slot:title>
|
<x-slot:title>Cloudflare Tunnel Configuration</x-slot:title>
|
||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
<livewire:activity-monitor header="Logs" showWaiting fullHeight />
|
<livewire:activity-monitor header="Logs" fullHeight />
|
||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
<form @submit.prevent="$wire.dispatch('automatedCloudflareConfig')"
|
<form @submit.prevent="$wire.dispatch('automatedCloudflareConfig')"
|
||||||
|
@@ -1,41 +1,58 @@
|
|||||||
<div class="pb-6">
|
<div class="pb-6">
|
||||||
<x-modal modalId="startProxy">
|
<x-slide-over @startproxy.window="slideOverOpen = true" fullScreen>
|
||||||
<x-slot:modalBody>
|
<x-slot:title>Proxy Startup Logs</x-slot:title>
|
||||||
<livewire:activity-monitor header="Proxy Startup Logs" />
|
<x-slot:content>
|
||||||
</x-slot:modalBody>
|
<livewire:activity-monitor header="Logs" fullHeight />
|
||||||
<x-slot:modalSubmit>
|
</x-slot:content>
|
||||||
<x-forms.button onclick="startProxy.close()" type="submit">
|
</x-slide-over>
|
||||||
Close
|
|
||||||
</x-forms.button>
|
|
||||||
</x-slot:modalSubmit>
|
|
||||||
</x-modal>
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h1>Server</h1>
|
<h1>Server</h1>
|
||||||
@if ($server->proxySet())
|
@if ($server->proxySet())
|
||||||
<div class="flex gap-2">
|
<div class="flex">
|
||||||
@if (data_get($server, 'proxy.force_stop', false) === false)
|
<div class="flex items-center">
|
||||||
<x-forms.button wire:click='checkProxyStatus()' :disabled="$isChecking" wire:loading.attr="disabled"
|
@if ($proxyStatus === 'running')
|
||||||
wire:target="checkProxyStatus">
|
<x-status.running status="Proxy Running" noLoading />
|
||||||
<span wire:loading.remove wire:target="checkProxyStatus">Refresh</span>
|
@elseif ($proxyStatus === 'restarting')
|
||||||
<span wire:loading wire:target="checkProxyStatus">Checking...</span>
|
<x-status.restarting status="Proxy Restarting" noLoading />
|
||||||
</x-forms.button>
|
@elseif ($proxyStatus === 'stopping')
|
||||||
@endif
|
<x-status.restarting status="Proxy Stopping" noLoading />
|
||||||
|
@elseif ($proxyStatus === 'starting')
|
||||||
<div wire:loading.remove wire:target="checkProxyStatus" class="flex items-center gap-2">
|
<x-status.restarting status="Proxy Starting" noLoading />
|
||||||
@if (data_get($server, 'proxy.status') === 'running')
|
|
||||||
<x-status.running status="Proxy Running" />
|
|
||||||
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
|
||||||
<x-status.restarting status="Proxy Restarting" />
|
|
||||||
@elseif (data_get($server, 'proxy.force_stop'))
|
@elseif (data_get($server, 'proxy.force_stop'))
|
||||||
<x-status.stopped status="Proxy Stopped" />
|
<x-status.stopped status="Proxy Stopped (Force Stop)" noLoading />
|
||||||
@elseif (data_get($server, 'proxy.status') === 'exited')
|
<div wire:loading wire:target="checkProxy" class="badge badge-warning"></div>
|
||||||
<x-status.stopped status="Proxy Exited" />
|
<div wire:loading wire:target="checkProxy"
|
||||||
@else
|
class="pl-2 pr-1 text-xs font-bold tracking-wider text-warning">
|
||||||
<x-status.stopped status="Proxy Not Running" />
|
Checking Ports Availability...
|
||||||
|
</div>
|
||||||
|
@elseif ($proxyStatus === 'exited')
|
||||||
|
<x-status.stopped status="Proxy Exited" noLoading />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if ($proxyStatus !== 'exited')
|
||||||
|
<div wire:loading>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg class="w-4 h-4 mx-1 ml-3 text-coollabs dark:text-warning animate-spin"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10"
|
||||||
|
stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button title="Refresh Status" wire:click='checkProxyStatus'
|
||||||
|
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
|
||||||
|
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="subtitle">{{ data_get($server, 'name') }}</div>
|
<div class="subtitle">{{ data_get($server, 'name') }}</div>
|
||||||
@@ -85,7 +102,7 @@
|
|||||||
<livewire:activity-monitor header="Logs" />
|
<livewire:activity-monitor header="Logs" />
|
||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
@if (data_get($server, 'proxy.status') === 'running')
|
@if ($proxyStatus === 'running')
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<div class="mt-1" wire:loading wire:target="loadProxyConfiguration">
|
<div class="mt-1" wire:loading wire:target="loadProxyConfiguration">
|
||||||
<x-loading text="Checking Traefik dashboard" />
|
<x-loading text="Checking Traefik dashboard" />
|
||||||
@@ -155,7 +172,6 @@
|
|||||||
<script>
|
<script>
|
||||||
$wire.$on('checkProxyEvent', () => {
|
$wire.$on('checkProxyEvent', () => {
|
||||||
try {
|
try {
|
||||||
$wire.$dispatch('info', 'Checking if the required ports are not used by other services.');
|
|
||||||
$wire.$call('checkProxy');
|
$wire.$call('checkProxy');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -171,7 +187,6 @@
|
|||||||
$wire.$call('startProxy');
|
$wire.$call('startProxy');
|
||||||
});
|
});
|
||||||
$wire.$on('stopEvent', () => {
|
$wire.$on('stopEvent', () => {
|
||||||
$wire.$dispatch('info', 'Stopping proxy.');
|
|
||||||
$wire.$call('stop');
|
$wire.$call('stop');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@@ -120,7 +120,7 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
<livewire:activity-monitor header="Docker Installation Logs" />
|
<livewire:activity-monitor header="Docker Installation Logs" :showWaiting="false" />
|
||||||
@isset($error)
|
@isset($error)
|
||||||
<pre class="font-bold whitespace-pre-line text-error">{!! $error !!}</pre>
|
<pre class="font-bold whitespace-pre-line text-error">{!! $error !!}</pre>
|
||||||
@endisset
|
@endisset
|
||||||
|
Reference in New Issue
Block a user