refactor(proxy-status): refactored how the proxy status is handled on the UI and on the backend
feat(cloudflare): improved cloudflare tunnel automated installation
This commit is contained in:
@@ -104,14 +104,11 @@ class RunRemoteProcess
|
|||||||
$this->activity->save();
|
$this->activity->save();
|
||||||
if ($this->call_event_on_finish) {
|
if ($this->call_event_on_finish) {
|
||||||
try {
|
try {
|
||||||
if ($this->call_event_data) {
|
$eventClass = "App\\Events\\$this->call_event_on_finish";
|
||||||
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
if (! is_null($this->call_event_data)) {
|
||||||
'data' => $this->call_event_data,
|
event(new $eventClass($this->call_event_data));
|
||||||
]));
|
|
||||||
} else {
|
} else {
|
||||||
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
event(new $eventClass($this->activity->causer_id));
|
||||||
'userId' => $this->activity->causer_id,
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Log::error('Error calling event: '.$e->getMessage());
|
Log::error('Error calling event: '.$e->getMessage());
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
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 Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -12,12 +13,38 @@ class CheckProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
// It should return if the proxy should be started (true) or not (false)
|
/**
|
||||||
public function handle(Server $server, $fromUI = false): bool
|
* Determine if the proxy should be started
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
@@ -26,86 +53,308 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine proxy container name based on environment
|
|
||||||
$proxyContainerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
|
||||||
|
|
||||||
if ($server->isSwarm()) {
|
|
||||||
$status = getContainerStatus($server, $proxyContainerName);
|
|
||||||
$server->proxy->set('status', $status);
|
|
||||||
$server->save();
|
|
||||||
if ($status === 'running') {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
$status = getContainerStatus($server, $proxyContainerName);
|
|
||||||
if ($status === 'running') {
|
|
||||||
$server->proxy->set('status', 'running');
|
|
||||||
$server->save();
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check proxy status for swarm mode
|
||||||
|
*/
|
||||||
|
private function checkSwarmProxy(Server $server): bool
|
||||||
|
{
|
||||||
|
$proxyContainerName = 'coolify-proxy_traefik';
|
||||||
|
$status = getContainerStatus($server, $proxyContainerName);
|
||||||
|
|
||||||
|
// Update status if changed
|
||||||
|
if ($server->proxy->status !== $status) {
|
||||||
|
$server->proxy->set('status', $status);
|
||||||
|
$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') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cloudflare tunnel doesn't need proxy
|
||||||
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'];
|
|
||||||
|
|
||||||
foreach ($portsToCheck as $port) {
|
// Check for port conflicts
|
||||||
// Use the smart port checker that handles dual-stack properly
|
$portsToCheck = $this->getPortsToCheck($server);
|
||||||
if ($this->isPortConflict($server, $port, $proxyContainerName)) {
|
|
||||||
if ($fromUI) {
|
if (empty($portsToCheck)) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if ($server->proxyType() !== ProxyTypes::NONE->value) {
|
|
||||||
$proxyCompose = CheckConfiguration::run($server);
|
|
||||||
if (isset($proxyCompose)) {
|
|
||||||
$yaml = Yaml::parse($proxyCompose);
|
|
||||||
$portsToCheck = [];
|
|
||||||
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) {
|
|
||||||
$portsToCheck[] = str($port)->before(':')->value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$portsToCheck = [];
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('Error checking proxy: '.$e->getMessage());
|
|
||||||
}
|
|
||||||
if (count($portsToCheck) === 0) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->validatePortsAvailable($server, $portsToCheck, $proxyContainerName, $fromUI);
|
||||||
|
|
||||||
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
|
||||||
|
*/
|
||||||
|
private function extractPortsFromCompose(array $yaml, string $proxyType): 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)) {
|
||||||
|
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 [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
$output = instant_remote_process([$command], $server, true);
|
||||||
|
$results = $this->parseBatchPortCheckOutput($output, $portsToCheck);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
try {
|
||||||
|
if ($this->isPortConflict($server, $port, $proxyContainerName)) {
|
||||||
|
$conflicts[] = $port;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\Log::warning("Sequential port check failed for port $port: ".$e->getMessage());
|
||||||
|
// Continue checking other ports
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $conflicts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,137 +363,168 @@ class CheckProxy
|
|||||||
*/
|
*/
|
||||||
private function isPortConflict(Server $server, string $port, string $proxyContainerName): bool
|
private function isPortConflict(Server $server, string $port, string $proxyContainerName): bool
|
||||||
{
|
{
|
||||||
// First check if our own proxy is using this port (which is fine)
|
// 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);
|
||||||
|
|
||||||
// Our proxy is using the port, which is fine
|
return true; // Our proxy is using the port
|
||||||
|
} catch (\Throwable $e) {
|
||||||
return false;
|
return false;
|
||||||
} 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 = [
|
* Check port usage with ss command
|
||||||
// Set 1: Use ss to check listener counts by protocol stack
|
*/
|
||||||
[
|
private function checkWithSs(Server $server, string $port): ?bool
|
||||||
'available' => 'command -v ss >/dev/null 2>&1',
|
{
|
||||||
'check' => [
|
$commands = [
|
||||||
// Get listening process details
|
'command -v ss >/dev/null 2>&1',
|
||||||
"ss_output=\$(ss -Htuln state listening sport = :$port 2>/dev/null) && echo \"\$ss_output\"",
|
"ss_output=\$(ss -Htuln state listening sport = :$port 2>/dev/null) && echo \"\$ss_output\"",
|
||||||
// Count IPv4 listeners
|
|
||||||
"echo \"\$ss_output\" | grep -c ':$port '",
|
"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
|
$output = instant_remote_process($commands, $server, true);
|
||||||
foreach ($commandSets as $set) {
|
|
||||||
try {
|
|
||||||
// Check if the command is available
|
|
||||||
instant_remote_process([$set['available']], $server);
|
|
||||||
|
|
||||||
// Run the actual check commands
|
return $this->parsePortCheckOutput($output, $port);
|
||||||
$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($lines[0] ?? '');
|
|
||||||
$count = intval(trim($lines[1] ?? '0'));
|
|
||||||
|
|
||||||
// If no listeners or empty result, port is free
|
|
||||||
if ($count == 0 || empty($details)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to detect if this is our coolify-proxy
|
/**
|
||||||
if (strpos($details, 'docker') !== false || strpos($details, $proxyContainerName) !== false) {
|
* Check port usage with netstat command
|
||||||
// It's likely our docker or proxy, which is fine
|
*/
|
||||||
return false;
|
private function checkWithNetstat(Server $server, string $port): ?bool
|
||||||
|
{
|
||||||
|
$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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for dual-stack scenario - typically 1-2 listeners (IPv4+IPv6)
|
/**
|
||||||
// If exactly 2 listeners and both have same port, likely dual-stack
|
* Check port usage with lsof command
|
||||||
if ($count <= 2) {
|
*/
|
||||||
// Check if it looks like a standard dual-stack setup
|
private function checkWithLsof(Server $server, string $port): ?bool
|
||||||
$isDualStack = false;
|
{
|
||||||
|
$commands = [
|
||||||
|
'command -v lsof >/dev/null 2>&1',
|
||||||
|
"lsof -i :$port -P -n | grep 'LISTEN'",
|
||||||
|
"lsof -i :$port -P -n | grep 'LISTEN' | wc -l",
|
||||||
|
];
|
||||||
|
|
||||||
// Look for IPv4 and IPv6 in the listing (ss output format)
|
$output = instant_remote_process($commands, $server, true);
|
||||||
if (preg_match('/LISTEN.*:'.$port.'\s/', $details) &&
|
|
||||||
(preg_match('/\*:'.$port.'\s/', $details) ||
|
return $this->parsePortCheckOutput($output, $port);
|
||||||
preg_match('/:::'.$port.'\s/', $details))) {
|
|
||||||
$isDualStack = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For netstat format
|
/**
|
||||||
if (strpos($details, '0.0.0.0:'.$port) !== false &&
|
* Check port usage with netcat as fallback
|
||||||
strpos($details, ':::'.$port) !== false) {
|
*/
|
||||||
$isDualStack = true;
|
private function checkWithNetcat(Server $server, string $port): ?bool
|
||||||
}
|
{
|
||||||
|
|
||||||
// For lsof format (IPv4 and IPv6)
|
|
||||||
if (strpos($details, '*:'.$port) !== false &&
|
|
||||||
preg_match('/\*:'.$port.'.*IPv4/', $details) &&
|
|
||||||
preg_match('/\*:'.$port.'.*IPv6/', $details)) {
|
|
||||||
$isDualStack = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isDualStack) {
|
|
||||||
return false; // This is just a normal dual-stack setup
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,7 @@
|
|||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use App\Events\ProxyStarted;
|
use App\Events\ProxyStatusChanged;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
@@ -57,20 +57,19 @@ 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',
|
'docker compose up -d --wait',
|
||||||
"echo 'Successfully started coolify-proxy.'",
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($async) {
|
if ($async) {
|
||||||
return remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
|
return remote_process($commands, $server, callEventOnFinish: 'ProxyStatusChanged', callEventData: $server->id);
|
||||||
} else {
|
} else {
|
||||||
instant_remote_process($commands, $server);
|
instant_remote_process($commands, $server);
|
||||||
$server->proxy->set('status', 'running');
|
|
||||||
$server->proxy->set('type', $proxyType);
|
$server->proxy->set('type', $proxyType);
|
||||||
$server->save();
|
$server->save();
|
||||||
ProxyStarted::dispatch($server);
|
ProxyStatusChanged::dispatch($server->id);
|
||||||
|
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
|
use App\Events\ProxyStatusChanged;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
@@ -24,6 +25,8 @@ class StopProxy
|
|||||||
$server->save();
|
$server->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
|
} finally {
|
||||||
|
ProxyStatusChanged::dispatch($server->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
use App\Events\CloudflareTunnelConfigured;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Spatie\Activitylog\Models\Activity;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class ConfigureCloudflared
|
class ConfigureCloudflared
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, string $cloudflare_token)
|
public function handle(Server $server, string $cloudflare_token, string $ssh_domain): Activity
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$config = [
|
$config = [
|
||||||
@@ -24,6 +24,13 @@ class ConfigureCloudflared
|
|||||||
'command' => 'tunnel run',
|
'command' => 'tunnel run',
|
||||||
'environment' => [
|
'environment' => [
|
||||||
"TUNNEL_TOKEN={$cloudflare_token}",
|
"TUNNEL_TOKEN={$cloudflare_token}",
|
||||||
|
'TUNNEL_METRICS=127.0.0.1:60123',
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => ['CMD', 'cloudflared', 'tunnel', '--metrics', '127.0.0.1:60123', 'ready'],
|
||||||
|
'interval' => '5s',
|
||||||
|
'timeout' => '30s',
|
||||||
|
'retries' => 5,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -34,22 +41,20 @@ class ConfigureCloudflared
|
|||||||
'mkdir -p /tmp/cloudflared',
|
'mkdir -p /tmp/cloudflared',
|
||||||
'cd /tmp/cloudflared',
|
'cd /tmp/cloudflared',
|
||||||
"echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null",
|
"echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null",
|
||||||
|
'echo Pulling latest Cloudflare Tunnel image.',
|
||||||
'docker compose pull',
|
'docker compose pull',
|
||||||
'docker compose down -v --remove-orphans > /dev/null 2>&1',
|
'echo Stopping existing Cloudflare Tunnel container.',
|
||||||
'docker compose up -d --remove-orphans',
|
'docker rm -f coolify-cloudflared || true',
|
||||||
|
'echo Starting new Cloudflare Tunnel container.',
|
||||||
|
'docker compose up --wait --wait-timeout 15 --remove-orphans || docker logs coolify-cloudflared',
|
||||||
]);
|
]);
|
||||||
instant_remote_process($commands, $server);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$server->settings->is_cloudflare_tunnel = false;
|
|
||||||
$server->settings->save();
|
|
||||||
throw $e;
|
|
||||||
} finally {
|
|
||||||
CloudflareTunnelConfigured::dispatch($server->team_id);
|
|
||||||
|
|
||||||
$commands = collect([
|
return remote_process($commands, $server, callEventOnFinish: 'CloudflareTunnelChanged', callEventData: [
|
||||||
'rm -fr /tmp/cloudflared',
|
'server_id' => $server->id,
|
||||||
|
'ssh_domain' => $ssh_domain,
|
||||||
]);
|
]);
|
||||||
instant_remote_process($commands, $server);
|
} catch (\Throwable $e) {
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
app/Events/CloudflareTunnelChanged.php
Normal file
14
app/Events/CloudflareTunnelChanged.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class CloudflareTunnelChanged
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(public $data) {}
|
||||||
|
}
|
@@ -3,33 +3,12 @@
|
|||||||
namespace App\Events;
|
namespace App\Events;
|
||||||
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
use Illuminate\Broadcasting\PrivateChannel;
|
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class ProxyStatusChanged implements ShouldBroadcast
|
class ProxyStatusChanged
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public ?int $teamId = null;
|
public function __construct(public $data) {}
|
||||||
|
|
||||||
public function __construct($teamId = null)
|
|
||||||
{
|
|
||||||
if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) {
|
|
||||||
$teamId = auth()->user()->currentTeam()->id;
|
|
||||||
}
|
|
||||||
$this->teamId = $teamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function broadcastOn(): array
|
|
||||||
{
|
|
||||||
if (is_null($this->teamId)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
new PrivateChannel("team.{$this->teamId}"),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
35
app/Events/ProxyStatusChangedUI.php
Normal file
35
app/Events/ProxyStatusChangedUI.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ProxyStatusChangedUI implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public ?int $teamId = null;
|
||||||
|
|
||||||
|
public function __construct(int $teamId)
|
||||||
|
{
|
||||||
|
if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) {
|
||||||
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
if (is_null($this->teamId)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@@ -213,9 +213,11 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (! $application) {
|
if (! $application) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($application->status !== $containerStatus) {
|
||||||
$application->status = $containerStatus;
|
$application->status = $containerStatus;
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function updateApplicationPreviewStatus(string $applicationId, string $pullRequestId, string $containerStatus)
|
private function updateApplicationPreviewStatus(string $applicationId, string $pullRequestId, string $containerStatus)
|
||||||
{
|
{
|
||||||
@@ -249,9 +251,11 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($application->status !== 'exited') {
|
||||||
$application->status = 'exited';
|
$application->status = 'exited';
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,9 +290,11 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($applicationPreview->status !== 'exited') {
|
||||||
$applicationPreview->status = 'exited';
|
$applicationPreview->status = 'exited';
|
||||||
$applicationPreview->save();
|
$applicationPreview->save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,8 +324,10 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (! $database) {
|
if (! $database) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($database->status !== $containerStatus) {
|
||||||
$database->status = $containerStatus;
|
$database->status = $containerStatus;
|
||||||
$database->save();
|
$database->save();
|
||||||
|
}
|
||||||
if ($this->isRunning($containerStatus) && $tcpProxy) {
|
if ($this->isRunning($containerStatus) && $tcpProxy) {
|
||||||
$tcpProxyContainerFound = $this->containers->filter(function ($value, $key) use ($databaseUuid) {
|
$tcpProxyContainerFound = $this->containers->filter(function ($value, $key) use ($databaseUuid) {
|
||||||
return data_get($value, 'name') === "$databaseUuid-proxy" && data_get($value, 'state') === 'running';
|
return data_get($value, 'name') === "$databaseUuid-proxy" && data_get($value, 'state') === 'running';
|
||||||
@@ -339,8 +347,10 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$notFoundDatabaseUuids->each(function ($databaseUuid) {
|
$notFoundDatabaseUuids->each(function ($databaseUuid) {
|
||||||
$database = $this->databases->where('uuid', $databaseUuid)->first();
|
$database = $this->databases->where('uuid', $databaseUuid)->first();
|
||||||
if ($database) {
|
if ($database) {
|
||||||
|
if ($database->status !== 'exited') {
|
||||||
$database->status = 'exited';
|
$database->status = 'exited';
|
||||||
$database->save();
|
$database->save();
|
||||||
|
}
|
||||||
if ($database->is_public) {
|
if ($database->is_public) {
|
||||||
StopDatabaseProxy::dispatch($database);
|
StopDatabaseProxy::dispatch($database);
|
||||||
}
|
}
|
||||||
@@ -358,17 +368,21 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($subType === 'application') {
|
if ($subType === 'application') {
|
||||||
$application = $service->applications()->where('id', $subId)->first();
|
$application = $service->applications()->where('id', $subId)->first();
|
||||||
if ($application) {
|
if ($application) {
|
||||||
|
if ($application->status !== $containerStatus) {
|
||||||
$application->status = $containerStatus;
|
$application->status = $containerStatus;
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} elseif ($subType === 'database') {
|
} elseif ($subType === 'database') {
|
||||||
$database = $service->databases()->where('id', $subId)->first();
|
$database = $service->databases()->where('id', $subId)->first();
|
||||||
if ($database) {
|
if ($database) {
|
||||||
|
if ($database->status !== $containerStatus) {
|
||||||
$database->status = $containerStatus;
|
$database->status = $containerStatus;
|
||||||
$database->save();
|
$database->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function updateNotFoundServiceStatus()
|
private function updateNotFoundServiceStatus()
|
||||||
{
|
{
|
||||||
@@ -378,18 +392,22 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$notFoundServiceApplicationIds->each(function ($serviceApplicationId) {
|
$notFoundServiceApplicationIds->each(function ($serviceApplicationId) {
|
||||||
$application = ServiceApplication::find($serviceApplicationId);
|
$application = ServiceApplication::find($serviceApplicationId);
|
||||||
if ($application) {
|
if ($application) {
|
||||||
|
if ($application->status !== 'exited') {
|
||||||
$application->status = 'exited';
|
$application->status = 'exited';
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if ($notFoundServiceDatabaseIds->isNotEmpty()) {
|
if ($notFoundServiceDatabaseIds->isNotEmpty()) {
|
||||||
$notFoundServiceDatabaseIds->each(function ($serviceDatabaseId) {
|
$notFoundServiceDatabaseIds->each(function ($serviceDatabaseId) {
|
||||||
$database = ServiceDatabase::find($serviceDatabaseId);
|
$database = ServiceDatabase::find($serviceDatabaseId);
|
||||||
if ($database) {
|
if ($database) {
|
||||||
|
if ($database->status !== 'exited') {
|
||||||
$database->status = 'exited';
|
$database->status = 'exited';
|
||||||
$database->save();
|
$database->save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -36,9 +36,10 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
$this->server->proxy->force_stop = false;
|
$this->server->proxy->force_stop = false;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
|
||||||
StartProxy::run($this->server, force: true);
|
StartProxy::run($this->server, force: true);
|
||||||
|
|
||||||
CheckProxy::run($this->server, true);
|
// CheckProxy::run($this->server, true);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
|
66
app/Listeners/CloudflareTunnelChangedNotification.php
Normal file
66
app/Listeners/CloudflareTunnelChangedNotification.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Listeners;
|
||||||
|
|
||||||
|
use App\Events\CloudflareTunnelChanged;
|
||||||
|
use App\Events\CloudflareTunnelConfigured;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Sleep;
|
||||||
|
|
||||||
|
class CloudflareTunnelChangedNotification
|
||||||
|
{
|
||||||
|
public Server $server;
|
||||||
|
|
||||||
|
public function __construct() {}
|
||||||
|
|
||||||
|
public function handle(CloudflareTunnelChanged $event): void
|
||||||
|
{
|
||||||
|
$server_id = data_get($event, 'data.server_id');
|
||||||
|
$ssh_domain = data_get($event, 'data.ssh_domain');
|
||||||
|
|
||||||
|
$this->server = Server::find($server_id)->firstOrFail();
|
||||||
|
|
||||||
|
// Check if cloudflare tunnel is running (container is healthy) - try 3 times with 5 second intervals
|
||||||
|
$cloudflareHealthy = false;
|
||||||
|
$attempts = 3;
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $attempts; $i++) {
|
||||||
|
\Log::debug("Cloudflare health check attempt {$i}/{$attempts}", ['server_id' => $server_id]);
|
||||||
|
$result = instant_remote_process_with_timeout(['docker inspect coolify-cloudflared | jq -e ".[0].State.Health.Status == \"healthy\""'], $this->server, false, 10);
|
||||||
|
|
||||||
|
if (blank($result)) {
|
||||||
|
\Log::debug("Cloudflare Tunnels container not found on attempt {$i}", ['server_id' => $server_id]);
|
||||||
|
} elseif ($result === 'true') {
|
||||||
|
\Log::debug("Cloudflare Tunnels container healthy on attempt {$i}", ['server_id' => $server_id]);
|
||||||
|
$cloudflareHealthy = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
\Log::debug("Cloudflare Tunnels container not healthy on attempt {$i}", ['server_id' => $server_id, 'result' => $result]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep between attempts (except after the last attempt)
|
||||||
|
if ($i < $attempts) {
|
||||||
|
Sleep::for(5)->seconds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $cloudflareHealthy) {
|
||||||
|
\Log::error('Cloudflare Tunnels container failed all health checks.', ['server_id' => $server_id, 'attempts' => $attempts]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->server->settings->update([
|
||||||
|
'is_cloudflare_tunnel' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Only update IP if it's not already set to the ssh_domain or if it's empty
|
||||||
|
if ($this->server->ip !== $ssh_domain && ! empty($ssh_domain)) {
|
||||||
|
\Log::debug('Cloudflare Tunnels configuration updated - updating IP address.', ['old_ip' => $this->server->ip, 'new_ip' => $ssh_domain]);
|
||||||
|
$this->server->update(['ip' => $ssh_domain]);
|
||||||
|
} else {
|
||||||
|
\Log::debug('Cloudflare Tunnels configuration updated - IP address unchanged.', ['current_ip' => $this->server->ip]);
|
||||||
|
}
|
||||||
|
$teamId = $this->server->team_id;
|
||||||
|
CloudflareTunnelConfigured::dispatch($teamId);
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Listeners;
|
namespace App\Listeners;
|
||||||
|
|
||||||
use App\Events\ProxyStarted;
|
use App\Events\ProxyStarted;
|
||||||
|
use App\Events\ProxyStatusChanged;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
|
||||||
class ProxyStartedNotification
|
class ProxyStartedNotification
|
||||||
@@ -18,5 +19,6 @@ class ProxyStartedNotification
|
|||||||
$this->server->setupDynamicProxyConfiguration();
|
$this->server->setupDynamicProxyConfiguration();
|
||||||
$this->server->proxy->force_stop = false;
|
$this->server->proxy->force_stop = false;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
// ProxyStatusChanged::dispatch( $this->server->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
34
app/Listeners/ProxyStatusChangedNotification.php
Normal file
34
app/Listeners/ProxyStatusChangedNotification.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Listeners;
|
||||||
|
|
||||||
|
use App\Events\ProxyStatusChanged;
|
||||||
|
use App\Events\ProxyStatusChangedUI;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
|
||||||
|
|
||||||
|
class ProxyStatusChangedNotification implements ShouldQueueAfterCommit
|
||||||
|
{
|
||||||
|
public function __construct() {}
|
||||||
|
|
||||||
|
public function handle(ProxyStatusChanged $event)
|
||||||
|
{
|
||||||
|
$serverId = $event->data;
|
||||||
|
$server = Server::find($serverId);
|
||||||
|
if (is_null($server)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$proxyContainerName = 'coolify-proxy';
|
||||||
|
$status = getContainerStatus($server, $proxyContainerName);
|
||||||
|
$server->proxy->set('status', $status);
|
||||||
|
$server->save();
|
||||||
|
|
||||||
|
if ($status === 'running') {
|
||||||
|
$server->setupDefaultRedirect();
|
||||||
|
$server->setupDynamicProxyConfiguration();
|
||||||
|
$server->proxy->force_stop = false;
|
||||||
|
$server->save();
|
||||||
|
}
|
||||||
|
ProxyStatusChangedUI::dispatch($server->team_id);
|
||||||
|
}
|
||||||
|
}
|
95
app/Livewire/Server/CloudflareTunnel.php
Normal file
95
app/Livewire/Server/CloudflareTunnel.php
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Actions\Server\ConfigureCloudflared;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class CloudflareTunnel extends Component
|
||||||
|
{
|
||||||
|
public Server $server;
|
||||||
|
|
||||||
|
#[Validate(['required', 'string'])]
|
||||||
|
public string $cloudflare_token;
|
||||||
|
|
||||||
|
#[Validate(['required', 'string'])]
|
||||||
|
public string $ssh_domain;
|
||||||
|
|
||||||
|
#[Validate(['required', 'boolean'])]
|
||||||
|
public bool $isCloudflareTunnelsEnabled;
|
||||||
|
|
||||||
|
public function getListeners()
|
||||||
|
{
|
||||||
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
|
|
||||||
|
return [
|
||||||
|
"echo-private:team.{$teamId},CloudflareTunnelConfigured" => 'refresh',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh()
|
||||||
|
{
|
||||||
|
$this->mount($this->server->uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mount(string $server_uuid)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
|
||||||
|
if ($this->server->isLocalhost()) {
|
||||||
|
return redirect()->route('server.show', ['server_uuid' => $server_uuid]);
|
||||||
|
}
|
||||||
|
$this->isCloudflareTunnelsEnabled = $this->server->settings->is_cloudflare_tunnel;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toggleCloudflareTunnels()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
remote_process(['docker rm -f coolify-cloudflared'], $this->server, false, 10);
|
||||||
|
$this->isCloudflareTunnelsEnabled = false;
|
||||||
|
$this->server->settings->is_cloudflare_tunnel = false;
|
||||||
|
$this->server->settings->save();
|
||||||
|
if ($this->server->ip_previous) {
|
||||||
|
$this->server->update(['ip' => $this->server->ip_previous]);
|
||||||
|
$this->dispatch('success', 'Cloudflare Tunnel disabled.<br><br>Manually updated the server IP address to its previous IP address.');
|
||||||
|
} else {
|
||||||
|
$this->dispatch('success', 'Cloudflare Tunnel disabled. Please update the server IP address to its real IP address in the server settings.');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function manualCloudflareConfig()
|
||||||
|
{
|
||||||
|
$this->isCloudflareTunnelsEnabled = true;
|
||||||
|
$this->server->settings->is_cloudflare_tunnel = true;
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->server->refresh();
|
||||||
|
$this->dispatch('success', 'Cloudflare Tunnel enabled.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function automatedCloudflareConfig()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (str($this->ssh_domain)->contains('https://')) {
|
||||||
|
$this->ssh_domain = str($this->ssh_domain)->replace('https://', '')->replace('http://', '')->trim();
|
||||||
|
$this->ssh_domain = str($this->ssh_domain)->replace('/', '');
|
||||||
|
}
|
||||||
|
$activity = ConfigureCloudflared::run($this->server, $this->cloudflare_token, $this->ssh_domain);
|
||||||
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.cloudflare-tunnel');
|
||||||
|
}
|
||||||
|
}
|
@@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire\Server;
|
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use Livewire\Attributes\Validate;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class CloudflareTunnels extends Component
|
|
||||||
{
|
|
||||||
public Server $server;
|
|
||||||
|
|
||||||
#[Validate(['required', 'boolean'])]
|
|
||||||
public bool $isCloudflareTunnelsEnabled;
|
|
||||||
|
|
||||||
public function mount(string $server_uuid)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
|
|
||||||
if ($this->server->isLocalhost()) {
|
|
||||||
return redirect()->route('server.show', ['server_uuid' => $server_uuid]);
|
|
||||||
}
|
|
||||||
$this->isCloudflareTunnelsEnabled = $this->server->settings->is_cloudflare_tunnel;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSave()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->validate();
|
|
||||||
$this->server->settings->is_cloudflare_tunnel = $this->isCloudflareTunnelsEnabled;
|
|
||||||
$this->server->settings->save();
|
|
||||||
$this->dispatch('success', 'Server updated.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function manualCloudflareConfig()
|
|
||||||
{
|
|
||||||
$this->isCloudflareTunnelsEnabled = true;
|
|
||||||
$this->server->settings->is_cloudflare_tunnel = true;
|
|
||||||
$this->server->settings->save();
|
|
||||||
$this->server->refresh();
|
|
||||||
$this->dispatch('success', 'Cloudflare Tunnels enabled.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.server.cloudflare-tunnels');
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire\Server;
|
|
||||||
|
|
||||||
use App\Actions\Server\ConfigureCloudflared;
|
|
||||||
use App\Models\Server;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class ConfigureCloudflareTunnels extends Component
|
|
||||||
{
|
|
||||||
public $server_id;
|
|
||||||
|
|
||||||
public string $cloudflare_token;
|
|
||||||
|
|
||||||
public string $ssh_domain;
|
|
||||||
|
|
||||||
public function alreadyConfigured()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
|
|
||||||
$server->settings->is_cloudflare_tunnel = true;
|
|
||||||
$server->settings->save();
|
|
||||||
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
|
|
||||||
$this->dispatch('refreshServerShow');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (str($this->ssh_domain)->contains('https://')) {
|
|
||||||
$this->ssh_domain = str($this->ssh_domain)->replace('https://', '')->replace('http://', '')->trim();
|
|
||||||
// remove / from the end
|
|
||||||
$this->ssh_domain = str($this->ssh_domain)->replace('/', '');
|
|
||||||
}
|
|
||||||
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
|
|
||||||
ConfigureCloudflared::dispatch($server, $this->cloudflare_token);
|
|
||||||
$server->settings->is_cloudflare_tunnel = true;
|
|
||||||
$server->ip = $this->ssh_domain;
|
|
||||||
$server->save();
|
|
||||||
$server->settings->save();
|
|
||||||
$this->dispatch('info', 'Cloudflare Tunnels configuration started.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.server.configure-cloudflare-tunnels');
|
|
||||||
}
|
|
||||||
}
|
|
119
app/Livewire/Server/Navbar.php
Normal file
119
app/Livewire/Server/Navbar.php
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Actions\Proxy\StopProxy;
|
||||||
|
use App\Jobs\RestartProxyJob;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Navbar extends Component
|
||||||
|
{
|
||||||
|
public Server $server;
|
||||||
|
|
||||||
|
public bool $isChecking = false;
|
||||||
|
|
||||||
|
public ?string $currentRoute = null;
|
||||||
|
|
||||||
|
public function getListeners()
|
||||||
|
{
|
||||||
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
|
|
||||||
|
return [
|
||||||
|
"echo-private:team.{$teamId},ProxyStatusChangedUI" => 'showNotification',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mount(Server $server)
|
||||||
|
{
|
||||||
|
$this->server = $server;
|
||||||
|
$this->currentRoute = request()->route()->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restart()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
RestartProxyJob::dispatch($this->server);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkProxy()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
CheckProxy::run($this->server, true);
|
||||||
|
$this->dispatch('startProxy')->self();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function startProxy()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$activity = StartProxy::run($this->server, force: true);
|
||||||
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stop(bool $forceStop = true)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
StopProxy::dispatch($this->server, $forceStop);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkProxyStatus()
|
||||||
|
{
|
||||||
|
if ($this->isChecking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->isChecking = true;
|
||||||
|
CheckProxy::run($this->server, true);
|
||||||
|
$this->showNotification();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
} finally {
|
||||||
|
$this->isChecking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showNotification()
|
||||||
|
{
|
||||||
|
$status = $this->server->proxy->status ?? 'unknown';
|
||||||
|
$forceStop = $this->server->proxy->force_stop ?? false;
|
||||||
|
|
||||||
|
switch ($status) {
|
||||||
|
case 'running':
|
||||||
|
$this->dispatch('success', 'Proxy is running.');
|
||||||
|
break;
|
||||||
|
case 'restarting':
|
||||||
|
$this->dispatch('info', 'Initiating proxy restart.');
|
||||||
|
break;
|
||||||
|
case 'exited':
|
||||||
|
if ($forceStop) {
|
||||||
|
$this->dispatch('info', 'Proxy is stopped manually.');
|
||||||
|
} else {
|
||||||
|
$this->dispatch('info', 'Proxy is stopped manually. Starting in a moment.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->dispatch('warning', 'Proxy is not running.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.navbar');
|
||||||
|
}
|
||||||
|
}
|
@@ -19,7 +19,7 @@ class Proxy extends Component
|
|||||||
|
|
||||||
public ?string $redirect_url = null;
|
public ?string $redirect_url = null;
|
||||||
|
|
||||||
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
protected $listeners = ['saveConfiguration' => 'submit'];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'server.settings.generate_exact_labels' => 'required|boolean',
|
'server.settings.generate_exact_labels' => 'required|boolean',
|
||||||
@@ -32,10 +32,10 @@ class Proxy extends Component
|
|||||||
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function proxyStatusUpdated()
|
// public function proxyStatusUpdated()
|
||||||
{
|
// {
|
||||||
$this->dispatch('refresh')->self();
|
// $this->dispatch('refresh')->self();
|
||||||
}
|
// }
|
||||||
|
|
||||||
public function changeProxy()
|
public function changeProxy()
|
||||||
{
|
{
|
||||||
|
@@ -1,106 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire\Server\Proxy;
|
|
||||||
|
|
||||||
use App\Actions\Proxy\CheckProxy;
|
|
||||||
use App\Actions\Proxy\StartProxy;
|
|
||||||
use App\Actions\Proxy\StopProxy;
|
|
||||||
use App\Events\ProxyStatusChanged;
|
|
||||||
use App\Jobs\RestartProxyJob;
|
|
||||||
use App\Models\Server;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Deploy extends Component
|
|
||||||
{
|
|
||||||
public Server $server;
|
|
||||||
|
|
||||||
public bool $traefikDashboardAvailable = false;
|
|
||||||
|
|
||||||
public ?string $currentRoute = null;
|
|
||||||
|
|
||||||
public ?string $serverIp = null;
|
|
||||||
|
|
||||||
public function getListeners()
|
|
||||||
{
|
|
||||||
$teamId = auth()->user()->currentTeam()->id;
|
|
||||||
|
|
||||||
return [
|
|
||||||
"echo-private:team.{$teamId},ProxyStatusChanged" => 'proxyStarted',
|
|
||||||
'proxyStatusUpdated',
|
|
||||||
'traefikDashboardAvailable',
|
|
||||||
'serverRefresh' => 'proxyStatusUpdated',
|
|
||||||
'checkProxy',
|
|
||||||
'startProxy',
|
|
||||||
'proxyChanged' => 'proxyStatusUpdated',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
if ($this->server->id === 0) {
|
|
||||||
$this->serverIp = base_ip();
|
|
||||||
} else {
|
|
||||||
$this->serverIp = $this->server->ip;
|
|
||||||
}
|
|
||||||
$this->currentRoute = request()->route()->getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function traefikDashboardAvailable(bool $data)
|
|
||||||
{
|
|
||||||
$this->traefikDashboardAvailable = $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function proxyStarted()
|
|
||||||
{
|
|
||||||
CheckProxy::run($this->server, true);
|
|
||||||
$this->dispatch('proxyStatusUpdated');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function proxyStatusUpdated()
|
|
||||||
{
|
|
||||||
$this->server->refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function restart()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
RestartProxyJob::dispatch($this->server);
|
|
||||||
$this->dispatch('checkProxy');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkProxy()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
CheckProxy::run($this->server, true);
|
|
||||||
$this->dispatch('startProxyPolling');
|
|
||||||
$this->dispatch('proxyChecked');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function startProxy()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->server->proxy->force_stop = false;
|
|
||||||
$this->server->save();
|
|
||||||
$activity = StartProxy::run($this->server, force: true);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function stop(bool $forceStop = true)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
StopProxy::run($this->server, $forceStop);
|
|
||||||
$this->dispatch('proxyStatusUpdated');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -19,7 +19,7 @@ class DynamicConfigurations extends Component
|
|||||||
$teamId = auth()->user()->currentTeam()->id;
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"echo-private:team.{$teamId},ProxyStatusChanged" => 'loadDynamicConfigurations',
|
"echo-private:team.{$teamId},ProxyStatusChangedUI" => 'loadDynamicConfigurations',
|
||||||
'loadDynamicConfigurations',
|
'loadDynamicConfigurations',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -11,12 +11,12 @@ class Show extends Component
|
|||||||
|
|
||||||
public $parameters = [];
|
public $parameters = [];
|
||||||
|
|
||||||
protected $listeners = ['proxyStatusUpdated', 'proxyChanged' => 'proxyStatusUpdated'];
|
// protected $listeners = ['proxyStatusUpdated'];
|
||||||
|
|
||||||
public function proxyStatusUpdated()
|
// public function proxyStatusUpdated()
|
||||||
{
|
// {
|
||||||
$this->server->refresh();
|
// $this->server->refresh();
|
||||||
}
|
// }
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
@@ -1,77 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire\Server\Proxy;
|
|
||||||
|
|
||||||
use App\Actions\Docker\GetContainersStatus;
|
|
||||||
use App\Actions\Proxy\CheckProxy;
|
|
||||||
use App\Actions\Proxy\StartProxy;
|
|
||||||
use App\Models\Server;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Status extends Component
|
|
||||||
{
|
|
||||||
public Server $server;
|
|
||||||
|
|
||||||
public bool $polling = false;
|
|
||||||
|
|
||||||
public int $numberOfPolls = 0;
|
|
||||||
|
|
||||||
protected $listeners = [
|
|
||||||
'proxyStatusUpdated',
|
|
||||||
'startProxyPolling',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function startProxyPolling()
|
|
||||||
{
|
|
||||||
$this->checkProxy();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function proxyStatusUpdated()
|
|
||||||
{
|
|
||||||
$this->server->refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkProxy(bool $notification = false)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if ($this->polling) {
|
|
||||||
if ($this->numberOfPolls >= 10) {
|
|
||||||
$this->polling = false;
|
|
||||||
$this->numberOfPolls = 0;
|
|
||||||
$notification && $this->dispatch('error', 'Proxy is not running.');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->numberOfPolls++;
|
|
||||||
}
|
|
||||||
$shouldStart = CheckProxy::run($this->server, true);
|
|
||||||
if ($shouldStart) {
|
|
||||||
StartProxy::run($this->server, false);
|
|
||||||
}
|
|
||||||
$this->dispatch('proxyStatusUpdated');
|
|
||||||
if ($this->server->proxy->status === 'running') {
|
|
||||||
$this->polling = false;
|
|
||||||
$notification && $this->dispatch('success', 'Proxy is running.');
|
|
||||||
} elseif ($this->server->proxy->status === 'exited' and ! $this->server->proxy->force_stop) {
|
|
||||||
$notification && $this->dispatch('error', 'Proxy has exited.');
|
|
||||||
} elseif ($this->server->proxy->force_stop) {
|
|
||||||
$notification && $this->dispatch('error', 'Proxy is stopped manually.');
|
|
||||||
} else {
|
|
||||||
$notification && $this->dispatch('error', 'Proxy is not running.');
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getProxyStatus()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
GetContainersStatus::run($this->server);
|
|
||||||
// dispatch_sync(new ContainerStatusJob($this->server));
|
|
||||||
$this->dispatch('proxyStatusUpdated');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -86,10 +86,7 @@ class Show extends Component
|
|||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$teamId = auth()->user()->currentTeam()->id;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"echo-private:team.{$teamId},CloudflareTunnelConfigured" => 'refresh',
|
|
||||||
'refreshServerShow' => 'refresh',
|
'refreshServerShow' => 'refresh',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -211,7 +208,6 @@ class Show extends Component
|
|||||||
$this->server->settings->is_usable = $this->isUsable = true;
|
$this->server->settings->is_usable = $this->isUsable = true;
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
ServerReachabilityChanged::dispatch($this->server);
|
ServerReachabilityChanged::dispatch($this->server);
|
||||||
$this->dispatch('proxyStatusUpdated');
|
|
||||||
} else {
|
} else {
|
||||||
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error);
|
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error);
|
||||||
|
|
||||||
|
@@ -69,6 +69,11 @@ class Server extends BaseModel
|
|||||||
}
|
}
|
||||||
if ($server->ip) {
|
if ($server->ip) {
|
||||||
$payload['ip'] = str($server->ip)->trim();
|
$payload['ip'] = str($server->ip)->trim();
|
||||||
|
|
||||||
|
// Update ip_previous when ip is being changed
|
||||||
|
if ($server->isDirty('ip') && $server->getOriginal('ip')) {
|
||||||
|
$payload['ip_previous'] = $server->getOriginal('ip');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$server->forceFill($payload);
|
$server->forceFill($payload);
|
||||||
});
|
});
|
||||||
|
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Events\ProxyStarted;
|
|
||||||
use App\Listeners\MaintenanceModeDisabledNotification;
|
use App\Listeners\MaintenanceModeDisabledNotification;
|
||||||
use App\Listeners\MaintenanceModeEnabledNotification;
|
use App\Listeners\MaintenanceModeEnabledNotification;
|
||||||
use App\Listeners\ProxyStartedNotification;
|
|
||||||
use Illuminate\Foundation\Events\MaintenanceModeDisabled;
|
use Illuminate\Foundation\Events\MaintenanceModeDisabled;
|
||||||
use Illuminate\Foundation\Events\MaintenanceModeEnabled;
|
use Illuminate\Foundation\Events\MaintenanceModeEnabled;
|
||||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||||
@@ -30,9 +28,6 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
GoogleExtendSocialite::class.'@handle',
|
GoogleExtendSocialite::class.'@handle',
|
||||||
InfomaniakExtendSocialite::class.'@handle',
|
InfomaniakExtendSocialite::class.'@handle',
|
||||||
],
|
],
|
||||||
ProxyStarted::class => [
|
|
||||||
ProxyStartedNotification::class,
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
@@ -42,6 +37,6 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
public function shouldDiscoverEvents(): bool
|
public function shouldDiscoverEvents(): bool
|
||||||
{
|
{
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -91,21 +91,17 @@ function connectProxyToNetworks(Server $server)
|
|||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
$commands = $networks->map(function ($network) {
|
$commands = $networks->map(function ($network) {
|
||||||
return [
|
return [
|
||||||
"echo 'Connecting coolify-proxy to $network network...'",
|
|
||||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --driver overlay --attachable $network >/dev/null",
|
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --driver overlay --attachable $network >/dev/null",
|
||||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||||
"echo 'Successfully connected coolify-proxy to $network network.'",
|
"echo 'Successfully connected coolify-proxy to $network network.'",
|
||||||
"echo 'Proxy started and configured successfully!'",
|
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$commands = $networks->map(function ($network) {
|
$commands = $networks->map(function ($network) {
|
||||||
return [
|
return [
|
||||||
"echo 'Connecting coolify-proxy to $network network...'",
|
|
||||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
||||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||||
"echo 'Successfully connected coolify-proxy to $network network.'",
|
"echo 'Successfully connected coolify-proxy to $network network.'",
|
||||||
"echo 'Proxy started and configured successfully!'",
|
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('servers', function (Blueprint $table) {
|
||||||
|
$table->string('ip_previous')->nullable()->after('ip');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('servers', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('ip_previous');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -1,60 +0,0 @@
|
|||||||
<div class="pb-6">
|
|
||||||
<x-modal modalId="startProxy">
|
|
||||||
<x-slot:modalBody>
|
|
||||||
<livewire:activity-monitor header="Proxy Startup Logs" />
|
|
||||||
</x-slot:modalBody>
|
|
||||||
<x-slot:modalSubmit>
|
|
||||||
<x-forms.button onclick="startProxy.close()" type="submit">
|
|
||||||
Close
|
|
||||||
</x-forms.button>
|
|
||||||
</x-slot:modalSubmit>
|
|
||||||
</x-modal>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<h1>Server</h1>
|
|
||||||
@if ($server->proxySet())
|
|
||||||
<livewire:server.proxy.status :server="$server" />
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
<div class="subtitle">{{ data_get($server, 'name') }}</div>
|
|
||||||
<div class="navbar-main">
|
|
||||||
<nav class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
|
|
||||||
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('server.show', [
|
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
|
||||||
]) }}">
|
|
||||||
<button>Configuration</button>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
|
|
||||||
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('server.proxy', [
|
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
|
||||||
]) }}">
|
|
||||||
<button>Proxy</button>
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('server.resources', [
|
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
|
||||||
]) }}">
|
|
||||||
<button>Resources</button>
|
|
||||||
</a>
|
|
||||||
<a class="{{ request()->routeIs('server.command') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('server.command', [
|
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
|
||||||
]) }}">
|
|
||||||
<button>Terminal</button>
|
|
||||||
</a>
|
|
||||||
<a class="{{ request()->routeIs('server.security.patches') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('server.security.patches', [
|
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
|
||||||
]) }}">
|
|
||||||
<button>Security</button>
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
<div class="order-first sm:order-last">
|
|
||||||
<livewire:server.proxy.deploy :server="$server" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@@ -13,9 +13,9 @@
|
|||||||
href="{{ route('server.ca-certificate', ['server_uuid' => $server->uuid]) }}">CA Certificate
|
href="{{ route('server.ca-certificate', ['server_uuid' => $server->uuid]) }}">CA Certificate
|
||||||
</a>
|
</a>
|
||||||
@if (!$server->isLocalhost())
|
@if (!$server->isLocalhost())
|
||||||
<a class="menu-item {{ $activeMenu === 'cloudflare-tunnels' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'cloudflare-tunnel' ? 'menu-item-active' : '' }}"
|
||||||
href="{{ route('server.cloudflare-tunnels', ['server_uuid' => $server->uuid]) }}">Cloudflare
|
href="{{ route('server.cloudflare-tunnel', ['server_uuid' => $server->uuid]) }}">Cloudflare
|
||||||
Tunnels</a>
|
Tunnel</a>
|
||||||
@endif
|
@endif
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<a class="menu-item {{ $activeMenu === 'docker-cleanup' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'docker-cleanup' ? 'menu-item-active' : '' }}"
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
<livewire:project.shared.configuration-checker :resource="$resource" />
|
<livewire:project.shared.configuration-checker :resource="$resource" />
|
||||||
<livewire:project.service.heading :service="$resource" :parameters="$parameters" title="Terminal" />
|
<livewire:project.service.heading :service="$resource" :parameters="$parameters" title="Terminal" />
|
||||||
@elseif ($type === 'server')
|
@elseif ($type === 'server')
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<livewire:server.navbar :server="$server" :parameters="$parameters" />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<h2 class="pt-4">Terminal</h2>
|
<h2 class="pt-4">Terminal</h2>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Advanced | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Advanced | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<livewire:server.navbar :server="$server" />
|
||||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="advanced" />
|
<x-server.sidebar :server="$server" activeMenu="advanced" />
|
||||||
<form wire:submit='submit' class="w-full">
|
<form wire:submit='submit' class="w-full">
|
||||||
|
@@ -2,12 +2,12 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > CA Certificate | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > CA Certificate | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<livewire:server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="ca-certificate" />
|
<x-server.sidebar :server="$server" activeMenu="ca-certificate" />
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h3>CA SSL Certificate</h3>
|
<h2>CA Certificate</h2>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-modal-confirmation title="Confirm changing of CA Certificate?" buttonTitle="Save"
|
<x-modal-confirmation title="Confirm changing of CA Certificate?" buttonTitle="Save"
|
||||||
submitAction="saveCaCertificate" :actions="[
|
submitAction="saveCaCertificate" :actions="[
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Metrics | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Metrics | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<livewire:server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="metrics" />
|
<x-server.sidebar :server="$server" activeMenu="metrics" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
122
resources/views/livewire/server/cloudflare-tunnel.blade.php
Normal file
122
resources/views/livewire/server/cloudflare-tunnel.blade.php
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<div>
|
||||||
|
<x-slot:title>
|
||||||
|
{{ data_get_str($server, 'name')->limit(10) }} > Cloudflare Tunnel | Coolify
|
||||||
|
</x-slot>
|
||||||
|
<livewire:server.navbar :server="$server" />
|
||||||
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
|
<x-server.sidebar :server="$server" activeMenu="cloudflare-tunnel" />
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<h2>Cloudflare Tunnel</h2>
|
||||||
|
<x-helper class="inline-flex"
|
||||||
|
helper="If you are using Cloudflare Tunnel, enable this. It will proxy all SSH requests to your server through Cloudflare.<br> You then can close your server's SSH port in the firewall of your hosting provider.<br><span class='dark:text-warning'>If you choose manual configuration, Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
|
||||||
|
@if ($isCloudflareTunnelsEnabled)
|
||||||
|
<span
|
||||||
|
class="px-2 py-1 text-xs font-semibold text-green-800 bg-green-100 rounded dark:text-green-100 dark:bg-green-800">
|
||||||
|
Enabled
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>Secure your servers with Cloudflare Tunnel.</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2 pt-6">
|
||||||
|
@if ($isCloudflareTunnelsEnabled)
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div
|
||||||
|
class="w-full px-4 py-2 text-yellow-800 rounded-xs border-l-4 border-yellow-500 bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300 dark:border-yellow-600">
|
||||||
|
<p class="font-bold">Warning!</p>
|
||||||
|
<p>If you disable Cloudflare Tunnel, you will need to update the server's IP address back
|
||||||
|
to
|
||||||
|
its real IP address in the server "General" settings. The server may become inaccessible
|
||||||
|
if the IP
|
||||||
|
address is not updated correctly.</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-64">
|
||||||
|
@if ($server->ip_previous)
|
||||||
|
<x-modal-confirmation title="Disable Cloudflare Tunnel?"
|
||||||
|
buttonTitle="Disable Cloudflare Tunnel" isErrorButton
|
||||||
|
submitAction="toggleCloudflareTunnels" :actions="[
|
||||||
|
'Cloudflare Tunnel will be disabled for this server.',
|
||||||
|
'The server IP address will be updated to its previous IP address.',
|
||||||
|
]"
|
||||||
|
confirmationText="DISABLE CLOUDFLARE TUNNEL"
|
||||||
|
confirmationLabel="Please type the confirmation text to disable Cloudflare Tunnel."
|
||||||
|
shortConfirmationLabel="Confirmation text"
|
||||||
|
step3ButtonText="Disable Cloudflare Tunnel" />
|
||||||
|
@else
|
||||||
|
<x-modal-confirmation title="Disable Cloudflare Tunnel?"
|
||||||
|
buttonTitle="Disable Cloudflare Tunnel" isErrorButton
|
||||||
|
submitAction="toggleCloudflareTunnels" :actions="[
|
||||||
|
'Cloudflare Tunnel will be disabled for this server.',
|
||||||
|
'You will need to update the server IP address to its real IP address.',
|
||||||
|
'The server may become inaccessible if the IP address is not updated correctly.',
|
||||||
|
'SSH access will revert to the standard port configuration.',
|
||||||
|
]"
|
||||||
|
confirmationText="DISABLE CLOUDFLARE TUNNEL"
|
||||||
|
confirmationLabel="Please type the confirmation text to disable Cloudflare Tunnel."
|
||||||
|
shortConfirmationLabel="Confirmation text"
|
||||||
|
step3ButtonText="Disable Cloudflare Tunnel" />
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@elseif (!$server->isFunctional())
|
||||||
|
<div
|
||||||
|
class="p-4 mb-4 w-full text-sm text-yellow-800 bg-yellow-100 rounded-sm dark:bg-yellow-900 dark:text-yellow-300">
|
||||||
|
To <span class="font-semibold">automatically</span> configure Cloudflare Tunnel, please
|
||||||
|
validate your server first.</span> Then you will need a Cloudflare token and an SSH
|
||||||
|
domain configured.
|
||||||
|
<br />
|
||||||
|
To <span class="font-semibold">manually</span> configure Cloudflare Tunnel, please
|
||||||
|
click <span wire:click="manualCloudflareConfig" class="underline cursor-pointer">here</span>,
|
||||||
|
then you should validate the server.
|
||||||
|
<br /><br />
|
||||||
|
For more information, please read our <a
|
||||||
|
href="https://coolify.io/docs/knowledge-base/cloudflare/tunnels/server-ssh" target="_blank"
|
||||||
|
class="underline ">documentation</a>.
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (!$isCloudflareTunnelsEnabled && $server->isFunctional())
|
||||||
|
<h3>Automated <a href="https://coolify.io/docs/knowledge-base/cloudflare/tunnels/server-ssh"
|
||||||
|
target="_blank"
|
||||||
|
class="text-xs underline hover:text-yellow-600 dark:hover:text-yellow-200">Docs</a></h3>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-slide-over @automated.window="slideOverOpen = true" fullScreen>
|
||||||
|
<x-slot:title>Cloudflare Tunnel Configuration</x-slot:title>
|
||||||
|
<x-slot:content>
|
||||||
|
<livewire:activity-monitor header="Logs" showWaiting fullHeight />
|
||||||
|
</x-slot:content>
|
||||||
|
</x-slide-over>
|
||||||
|
<form @submit.prevent="$wire.dispatch('automatedCloudflareConfig')"
|
||||||
|
class="flex flex-col gap-2 w-full">
|
||||||
|
<x-forms.input id="cloudflare_token" required label="Cloudflare Token" type="password" />
|
||||||
|
<x-forms.input id="ssh_domain" label="Configured SSH Domain" required
|
||||||
|
helper="The SSH domain you configured in Cloudflare. Make sure there is no protocol like http(s):// so you provide a FQDN not a URL. <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels/server-ssh' target='_blank'>Documentation</a>" />
|
||||||
|
<x-forms.button type="submit" isHighlighted>Continue</x-forms.button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@script
|
||||||
|
<script>
|
||||||
|
$wire.$on('automatedCloudflareConfig', () => {
|
||||||
|
window.dispatchEvent(new CustomEvent('automated'));
|
||||||
|
$wire.$call('automatedCloudflareConfig');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endscript
|
||||||
|
</div>
|
||||||
|
<h3 class="pt-6 pb-2">Manual</h3>
|
||||||
|
<x-modal-confirmation buttonFullWidth title="I manually configured Cloudflare Tunnel?"
|
||||||
|
buttonTitle="I manually configured Cloudflare Tunnel" submitAction="manualCloudflareConfig"
|
||||||
|
:actions="[
|
||||||
|
'You set everything up manually, including in Cloudflare and on the server (cloudflared is running).',
|
||||||
|
'If you missed something, the connection will not work.',
|
||||||
|
]" confirmationText="I manually configured Cloudflare Tunnel"
|
||||||
|
confirmationLabel="Please type the confirmation text to confirm that you manually configured Cloudflare Tunnel."
|
||||||
|
shortConfirmationLabel="Confirmation text" step3ButtonText="Confirm" />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -1,53 +0,0 @@
|
|||||||
<div>
|
|
||||||
<x-slot:title>
|
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Cloudflare Tunnels | Coolify
|
|
||||||
</x-slot>
|
|
||||||
<x-server.navbar :server="$server" />
|
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
|
||||||
<x-server.sidebar :server="$server" activeMenu="cloudflare-tunnels" />
|
|
||||||
<div class="w-full">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex gap-1 items-center">
|
|
||||||
<h2>Cloudflare Tunnels</h2>
|
|
||||||
<x-helper class="inline-flex"
|
|
||||||
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br> You then can close your server's SSH port in the firewall of your hosting provider.<br><span class='dark:text-warning'>If you choose manual configuration, Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
|
|
||||||
</div>
|
|
||||||
<div>Secure your servers with Cloudflare Tunnels.</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 pt-6">
|
|
||||||
@if ($isCloudflareTunnelsEnabled)
|
|
||||||
<div class="w-64">
|
|
||||||
<x-forms.checkbox instantSave id="isCloudflareTunnelsEnabled" label="Enabled" />
|
|
||||||
</div>
|
|
||||||
@elseif (!$server->isFunctional())
|
|
||||||
<div
|
|
||||||
class="p-4 mb-4 w-full text-sm text-yellow-800 bg-yellow-100 rounded-sm dark:bg-yellow-900 dark:text-yellow-300">
|
|
||||||
To <span class="font-semibold">automatically</span> configure Cloudflare Tunnels, please
|
|
||||||
validate your server first.</span> Then you will need a Cloudflare token and an SSH
|
|
||||||
domain configured.
|
|
||||||
<br />
|
|
||||||
To <span class="font-semibold">manually</span> configure Cloudflare Tunnels, please
|
|
||||||
click <span wire:click="manualCloudflareConfig" class="underline cursor-pointer">here</span>,
|
|
||||||
then you should validate the server.
|
|
||||||
<br /><br />
|
|
||||||
For more information, please read our <a
|
|
||||||
href="https://coolify.io/docs/knowledge-base/cloudflare/tunnels/overview" target="_blank"
|
|
||||||
class="font-medium underline hover:text-yellow-600 dark:hover:text-yellow-200">documentation</a>.
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if (!$isCloudflareTunnelsEnabled && $server->isFunctional())
|
|
||||||
<h4>Configuration</h4>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<x-modal-input buttonTitle="Automated" title="Cloudflare Tunnels" :closeOutside="false"
|
|
||||||
isHighlightedButton>
|
|
||||||
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
|
|
||||||
</x-modal-input>
|
|
||||||
<x-forms.button wire:click="manualCloudflareConfig" class="w-20">
|
|
||||||
Manual
|
|
||||||
</x-forms.button>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@@ -1,6 +0,0 @@
|
|||||||
<form wire:submit.prevent='submit' class="flex flex-col gap-2 w-full">
|
|
||||||
<x-forms.input id="cloudflare_token" required label="Cloudflare Token" type="password" />
|
|
||||||
<x-forms.input id="ssh_domain" label="Configured SSH Domain" required
|
|
||||||
helper="The SSH domain you configured in Cloudflare. Make sure there is no protocol like http(s):// so you provide a FQDN not a URL. <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels/server-ssh' target='_blank'>Documentation</a>" />
|
|
||||||
<x-forms.button type="submit" isHighlighted @click="modalOpen=false">Continue</x-forms.button>
|
|
||||||
</form>
|
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Delete Server | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Delete Server | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<livewire:server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="danger" />
|
<x-server.sidebar :server="$server" activeMenu="danger" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Destinations | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Destinations | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<livewire:server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="destinations" />
|
<x-server.sidebar :server="$server" activeMenu="destinations" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Docker Cleanup | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Docker Cleanup | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<livewire:server.navbar :server="$server" />
|
||||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="docker-cleanup" />
|
<x-server.sidebar :server="$server" activeMenu="docker-cleanup" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
@@ -77,7 +77,8 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h3 class="mb-4">Recent executions <span class="text-xs text-neutral-500">(click to check output)</span></h3>
|
<h3 class="mb-4">Recent executions <span class="text-xs text-neutral-500">(click to check
|
||||||
|
output)</span></h3>
|
||||||
<livewire:server.docker-cleanup-executions :server="$server" />
|
<livewire:server.docker-cleanup-executions :server="$server" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Log Drains | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Log Drains | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<livewire:server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="log-drains" />
|
<x-server.sidebar :server="$server" activeMenu="log-drains" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
178
resources/views/livewire/server/navbar.blade.php
Normal file
178
resources/views/livewire/server/navbar.blade.php
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<div class="pb-6">
|
||||||
|
<x-modal modalId="startProxy">
|
||||||
|
<x-slot:modalBody>
|
||||||
|
<livewire:activity-monitor header="Proxy Startup Logs" />
|
||||||
|
</x-slot:modalBody>
|
||||||
|
<x-slot:modalSubmit>
|
||||||
|
<x-forms.button onclick="startProxy.close()" type="submit">
|
||||||
|
Close
|
||||||
|
</x-forms.button>
|
||||||
|
</x-slot:modalSubmit>
|
||||||
|
</x-modal>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<h1>Server</h1>
|
||||||
|
@if ($server->proxySet())
|
||||||
|
<div class="flex gap-2">
|
||||||
|
@if (data_get($server, 'proxy.force_stop', false) === false)
|
||||||
|
<x-forms.button wire:click='checkProxyStatus()' :disabled="$isChecking" wire:loading.attr="disabled"
|
||||||
|
wire:target="checkProxyStatus">
|
||||||
|
<span wire:loading.remove wire:target="checkProxyStatus">Refresh</span>
|
||||||
|
<span wire:loading wire:target="checkProxyStatus">Checking...</span>
|
||||||
|
</x-forms.button>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div wire:loading.remove wire:target="checkProxyStatus" class="flex items-center gap-2">
|
||||||
|
@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'))
|
||||||
|
<x-status.stopped status="Proxy Stopped" />
|
||||||
|
@elseif (data_get($server, 'proxy.status') === 'exited')
|
||||||
|
<x-status.stopped status="Proxy Exited" />
|
||||||
|
@else
|
||||||
|
<x-status.stopped status="Proxy Not Running" />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="subtitle">{{ data_get($server, 'name') }}</div>
|
||||||
|
<div class="navbar-main">
|
||||||
|
<nav
|
||||||
|
class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
|
||||||
|
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('server.show', [
|
||||||
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
|
]) }}">
|
||||||
|
<button>Configuration</button>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
|
||||||
|
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('server.proxy', [
|
||||||
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
|
]) }}">
|
||||||
|
<button>Proxy</button>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('server.resources', [
|
||||||
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
|
]) }}">
|
||||||
|
<button>Resources</button>
|
||||||
|
</a>
|
||||||
|
<a class="{{ request()->routeIs('server.command') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('server.command', [
|
||||||
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
|
]) }}">
|
||||||
|
<button>Terminal</button>
|
||||||
|
</a>
|
||||||
|
<a class="{{ request()->routeIs('server.security.patches') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('server.security.patches', [
|
||||||
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
|
]) }}">
|
||||||
|
<button>Security</button>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
<div class="order-first sm:order-last">
|
||||||
|
@php use App\Enums\ProxyTypes; @endphp
|
||||||
|
<div>
|
||||||
|
@if ($server->proxySet())
|
||||||
|
<x-slide-over fullScreen @startproxy.window="slideOverOpen = true">
|
||||||
|
<x-slot:title>Proxy Status</x-slot:title>
|
||||||
|
<x-slot:content>
|
||||||
|
<livewire:activity-monitor header="Logs" />
|
||||||
|
</x-slot:content>
|
||||||
|
</x-slide-over>
|
||||||
|
@if (data_get($server, 'proxy.status') === 'running')
|
||||||
|
<div class="flex gap-2">
|
||||||
|
@if (
|
||||||
|
$currentRoute === 'server.proxy' &&
|
||||||
|
$traefikDashboardAvailable &&
|
||||||
|
$server->proxyType() === ProxyTypes::TRAEFIK->value)
|
||||||
|
<button>
|
||||||
|
<a target="_blank" href="http://{{ $serverIp }}:8080">
|
||||||
|
Traefik Dashboard
|
||||||
|
<x-external-link />
|
||||||
|
</a>
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
<x-modal-confirmation title="Confirm Proxy Restart?" buttonTitle="Restart Proxy"
|
||||||
|
submitAction="restart" :actions="[
|
||||||
|
'This proxy will be stopped and started again.',
|
||||||
|
'All resources hosted on coolify will be unavailable during the restart.',
|
||||||
|
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||||
|
step2ButtonText="Restart Proxy" :dispatchEvent="true" dispatchEventType="restartEvent">
|
||||||
|
<x-slot:button-title>
|
||||||
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" stroke-width="2">
|
||||||
|
<path
|
||||||
|
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||||
|
<path d="M20 4v5h-5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Restart Proxy
|
||||||
|
</x-slot:button-title>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
<x-modal-confirmation title="Confirm Proxy Stopping?" buttonTitle="Stop Proxy"
|
||||||
|
submitAction="stop(true)" :actions="[
|
||||||
|
'The coolify proxy will be stopped.',
|
||||||
|
'All resources hosted on coolify will be unavailable.',
|
||||||
|
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||||
|
step2ButtonText="Stop Proxy" :dispatchEvent="true" dispatchEventType="stopEvent">
|
||||||
|
<x-slot:button-title>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error"
|
||||||
|
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path
|
||||||
|
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
Stop Proxy
|
||||||
|
</x-slot:button-title>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<button @click="$wire.dispatch('checkProxyEvent')" class="gap-2 button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
|
||||||
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M7 4v16l13 -8z" />
|
||||||
|
</svg>
|
||||||
|
Start Proxy
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
@script
|
||||||
|
<script>
|
||||||
|
$wire.$on('checkProxyEvent', () => {
|
||||||
|
$wire.$dispatch('info', 'Checking if the required ports are not used by other services.');
|
||||||
|
$wire.$call('checkProxy');
|
||||||
|
});
|
||||||
|
$wire.$on('restartEvent', () => {
|
||||||
|
$wire.$dispatch('info', 'Initiating proxy restart.');
|
||||||
|
$wire.$call('restart');
|
||||||
|
});
|
||||||
|
$wire.$on('startProxy', () => {
|
||||||
|
window.dispatchEvent(new CustomEvent('startproxy'))
|
||||||
|
$wire.$call('startProxy');
|
||||||
|
});
|
||||||
|
$wire.$on('stopEvent', () => {
|
||||||
|
$wire.$dispatch('info', 'Stopping proxy.');
|
||||||
|
$wire.$call('stop');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endscript
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Private Key | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Private Key | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<livewire:server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="private-key" />
|
<x-server.sidebar :server="$server" activeMenu="private-key" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
@@ -1,93 +0,0 @@
|
|||||||
@php use App\Enums\ProxyTypes; @endphp
|
|
||||||
<div>
|
|
||||||
@if ($server->proxySet())
|
|
||||||
<x-slide-over closeWithX fullScreen @startproxy.window="slideOverOpen = true">
|
|
||||||
<x-slot:title>Proxy Status</x-slot:title>
|
|
||||||
<x-slot:content>
|
|
||||||
<livewire:activity-monitor header="Logs" />
|
|
||||||
</x-slot:content>
|
|
||||||
</x-slide-over>
|
|
||||||
@if (data_get($server, 'proxy.status') === 'running')
|
|
||||||
<div class="flex gap-2">
|
|
||||||
@if (
|
|
||||||
$currentRoute === 'server.proxy' &&
|
|
||||||
$traefikDashboardAvailable &&
|
|
||||||
$server->proxyType() === ProxyTypes::TRAEFIK->value)
|
|
||||||
<button>
|
|
||||||
<a target="_blank" href="http://{{ $serverIp }}:8080">
|
|
||||||
Traefik Dashboard
|
|
||||||
<x-external-link />
|
|
||||||
</a>
|
|
||||||
</button>
|
|
||||||
@endif
|
|
||||||
<x-modal-confirmation title="Confirm Proxy Restart?" buttonTitle="Restart Proxy" submitAction="restart"
|
|
||||||
:actions="[
|
|
||||||
'This proxy will be stopped and started again.',
|
|
||||||
'All resources hosted on coolify will be unavailable during the restart.',
|
|
||||||
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Proxy"
|
|
||||||
:dispatchEvent="true" dispatchEventType="restartEvent">
|
|
||||||
<x-slot:button-title>
|
|
||||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
stroke-width="2">
|
|
||||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
|
||||||
<path d="M20 4v5h-5" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
Restart Proxy
|
|
||||||
</x-slot:button-title>
|
|
||||||
</x-modal-confirmation>
|
|
||||||
<x-modal-confirmation title="Confirm Proxy Stopping?" buttonTitle="Stop Proxy" submitAction="stop(true)"
|
|
||||||
:actions="[
|
|
||||||
'The coolify proxy will be stopped.',
|
|
||||||
'All resources hosted on coolify will be unavailable.',
|
|
||||||
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Stop Proxy" :dispatchEvent="true"
|
|
||||||
dispatchEventType="stopEvent">
|
|
||||||
<x-slot:button-title>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
|
||||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
||||||
stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
||||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
||||||
</path>
|
|
||||||
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
||||||
</path>
|
|
||||||
</svg>
|
|
||||||
Stop Proxy
|
|
||||||
</x-slot:button-title>
|
|
||||||
</x-modal-confirmation>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<button @click="$wire.dispatch('checkProxyEvent')" class="gap-2 button">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
||||||
stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M7 4v16l13 -8z" />
|
|
||||||
</svg>
|
|
||||||
Start Proxy
|
|
||||||
</button>
|
|
||||||
@endif
|
|
||||||
@endif
|
|
||||||
@script
|
|
||||||
<script>
|
|
||||||
$wire.$on('checkProxyEvent', () => {
|
|
||||||
$wire.$dispatch('info', 'Checking proxy.');
|
|
||||||
$wire.$call('checkProxy');
|
|
||||||
});
|
|
||||||
$wire.$on('restartEvent', () => {
|
|
||||||
$wire.$dispatch('info', 'Restarting proxy.');
|
|
||||||
$wire.$call('restart');
|
|
||||||
});
|
|
||||||
$wire.$on('proxyChecked', () => {
|
|
||||||
window.dispatchEvent(new CustomEvent('startproxy'))
|
|
||||||
$wire.$call('startProxy');
|
|
||||||
|
|
||||||
});
|
|
||||||
$wire.$on('stopEvent', () => {
|
|
||||||
$wire.$dispatch('info', 'Stopping proxy.');
|
|
||||||
$wire.$call('stop');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@endscript
|
|
||||||
</div>
|
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
Proxy Dynamic Configuration | Coolify
|
Proxy Dynamic Configuration | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<livewire:server.navbar :server="$server" :parameters="$parameters" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar-proxy :server="$server" :parameters="$parameters" />
|
<x-server.sidebar-proxy :server="$server" :parameters="$parameters" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
Proxy Logs | Coolify
|
Proxy Logs | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<livewire:server.navbar :server="$server" :parameters="$parameters" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar-proxy :server="$server" :parameters="$parameters" />
|
<x-server.sidebar-proxy :server="$server" :parameters="$parameters" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
Proxy Configuration | Coolify
|
Proxy Configuration | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<livewire:server.navbar :server="$server" :parameters="$parameters" />
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar-proxy :server="$server" :parameters="$parameters" />
|
<x-server.sidebar-proxy :server="$server" :parameters="$parameters" />
|
||||||
|
@@ -1,17 +0,0 @@
|
|||||||
<div @if (data_get($server, 'proxy.force_stop', false) === false) x-init="$wire.checkProxy()" @endif class="flex gap-2">
|
|
||||||
@if (data_get($server, 'proxy.force_stop', false) === false)
|
|
||||||
<x-forms.button wire:click='checkProxy(true)' :showLoadingIndicator="false">Refresh</x-forms.button>
|
|
||||||
@endif
|
|
||||||
@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'))
|
|
||||||
<x-status.stopped status="Proxy Stopped" />
|
|
||||||
@elseif (data_get($server, 'proxy.status') === 'exited')
|
|
||||||
<x-status.stopped status="Proxy Exited" />
|
|
||||||
@else
|
|
||||||
<x-status.stopped status="Proxy Not Running" />
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Server Resources | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Server Resources | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<livewire:server.navbar :server="$server" :parameters="$parameters" />
|
||||||
<div x-data="{ activeTab: 'managed' }" class="flex flex-col h-full gap-8 md:flex-row">
|
<div x-data="{ activeTab: 'managed' }" class="flex flex-col h-full gap-8 md:flex-row">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Security | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Security | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<livewire:server.navbar :server="$server" />
|
||||||
<x-slide-over closeWithX fullScreen @startupdate.window="slideOverOpen = true">
|
<x-slide-over closeWithX fullScreen @startupdate.window="slideOverOpen = true">
|
||||||
<x-slot:title>Updating Packages</x-slot:title>
|
<x-slot:title>Updating Packages</x-slot:title>
|
||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > General | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > General | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<livewire:server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="general" />
|
<x-server.sidebar :server="$server" activeMenu="general" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
@@ -39,7 +39,7 @@ use App\Livewire\Security\PrivateKey\Show as SecurityPrivateKeyShow;
|
|||||||
use App\Livewire\Server\Advanced as ServerAdvanced;
|
use App\Livewire\Server\Advanced as ServerAdvanced;
|
||||||
use App\Livewire\Server\CaCertificate\Show as CaCertificateShow;
|
use App\Livewire\Server\CaCertificate\Show as CaCertificateShow;
|
||||||
use App\Livewire\Server\Charts as ServerCharts;
|
use App\Livewire\Server\Charts as ServerCharts;
|
||||||
use App\Livewire\Server\CloudflareTunnels;
|
use App\Livewire\Server\CloudflareTunnel;
|
||||||
use App\Livewire\Server\Delete as DeleteServer;
|
use App\Livewire\Server\Delete as DeleteServer;
|
||||||
use App\Livewire\Server\Destinations as ServerDestinations;
|
use App\Livewire\Server\Destinations as ServerDestinations;
|
||||||
use App\Livewire\Server\DockerCleanup;
|
use App\Livewire\Server\DockerCleanup;
|
||||||
@@ -245,7 +245,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');
|
Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');
|
||||||
Route::get('/ca-certificate', CaCertificateShow::class)->name('server.ca-certificate');
|
Route::get('/ca-certificate', CaCertificateShow::class)->name('server.ca-certificate');
|
||||||
Route::get('/resources', ResourcesShow::class)->name('server.resources');
|
Route::get('/resources', ResourcesShow::class)->name('server.resources');
|
||||||
Route::get('/cloudflare-tunnels', CloudflareTunnels::class)->name('server.cloudflare-tunnels');
|
Route::get('/cloudflare-tunnel', CloudflareTunnel::class)->name('server.cloudflare-tunnel');
|
||||||
Route::get('/destinations', ServerDestinations::class)->name('server.destinations');
|
Route::get('/destinations', ServerDestinations::class)->name('server.destinations');
|
||||||
Route::get('/log-drains', LogDrains::class)->name('server.log-drains');
|
Route::get('/log-drains', LogDrains::class)->name('server.log-drains');
|
||||||
Route::get('/metrics', ServerCharts::class)->name('server.charts');
|
Route::get('/metrics', ServerCharts::class)->name('server.charts');
|
||||||
|
Reference in New Issue
Block a user