mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-19 00:15:01 +00:00
Merge remote-tracking branch 'origin/next' into env-var-descriptions
This commit is contained in:
commit
d9e39ba211
404 files changed
+29406
-26356
No files matched your search
@@ -63,20 +63,30 @@ class All extends Component
|
||||
|
||||
public function getEnvironmentVariablesProperty()
|
||||
{
|
||||
if ($this->is_env_sorting_enabled === false) {
|
||||
return $this->resource->environment_variables()->orderBy('order')->get();
|
||||
$query = $this->resource->environment_variables()
|
||||
->orderByRaw("CASE WHEN is_required = true AND (value IS NULL OR value = '') THEN 0 ELSE 1 END");
|
||||
|
||||
if ($this->is_env_sorting_enabled) {
|
||||
$query->orderBy('key');
|
||||
} else {
|
||||
$query->orderBy('order');
|
||||
}
|
||||
|
||||
return $this->resource->environment_variables;
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
public function getEnvironmentVariablesPreviewProperty()
|
||||
{
|
||||
if ($this->is_env_sorting_enabled === false) {
|
||||
return $this->resource->environment_variables_preview()->orderBy('order')->get();
|
||||
$query = $this->resource->environment_variables_preview()
|
||||
->orderByRaw("CASE WHEN is_required = true AND (value IS NULL OR value = '') THEN 0 ELSE 1 END");
|
||||
|
||||
if ($this->is_env_sorting_enabled) {
|
||||
$query->orderBy('key');
|
||||
} else {
|
||||
$query->orderBy('order');
|
||||
}
|
||||
|
||||
return $this->resource->environment_variables_preview;
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
public function getHardcodedEnvironmentVariablesProperty()
|
||||
|
||||
@@ -21,6 +21,10 @@ use Livewire\Component;
|
||||
|
||||
class GetLogs extends Component
|
||||
{
|
||||
public const MAX_LOG_LINES = 50000;
|
||||
|
||||
public const MAX_DOWNLOAD_SIZE_BYTES = 50 * 1024 * 1024; // 50MB
|
||||
|
||||
public string $outputs = '';
|
||||
|
||||
public string $errors = '';
|
||||
@@ -123,6 +127,9 @@ class GetLogs extends Component
|
||||
if ($this->numberOfLines <= 0 || is_null($this->numberOfLines)) {
|
||||
$this->numberOfLines = 1000;
|
||||
}
|
||||
if ($this->numberOfLines > self::MAX_LOG_LINES) {
|
||||
$this->numberOfLines = self::MAX_LOG_LINES;
|
||||
}
|
||||
if ($this->container) {
|
||||
if ($this->showTimeStamps) {
|
||||
if ($this->server->isSwarm()) {
|
||||
@@ -159,10 +166,12 @@ class GetLogs extends Component
|
||||
}
|
||||
// Collect new logs into temporary variable first to prevent flickering
|
||||
// (avoids clearing output before new data is ready)
|
||||
$newOutputs = '';
|
||||
Process::run($sshCommand, function (string $type, string $output) use (&$newOutputs) {
|
||||
$newOutputs .= removeAnsiColors($output);
|
||||
// Use array accumulation + implode for O(n) instead of O(n²) string concatenation
|
||||
$logChunks = [];
|
||||
Process::timeout(config('constants.ssh.command_timeout'))->run($sshCommand, function (string $type, string $output) use (&$logChunks) {
|
||||
$logChunks[] = removeAnsiColors($output);
|
||||
});
|
||||
$newOutputs = implode('', $logChunks);
|
||||
|
||||
if ($this->showTimeStamps) {
|
||||
$newOutputs = str($newOutputs)->split('/\n/')->sort(function ($a, $b) {
|
||||
@@ -183,6 +192,79 @@ class GetLogs extends Component
|
||||
return sanitizeLogsForExport($this->outputs);
|
||||
}
|
||||
|
||||
public function downloadAllLogs(): string
|
||||
{
|
||||
if (! $this->server->isFunctional() || ! $this->container) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($this->showTimeStamps) {
|
||||
if ($this->server->isSwarm()) {
|
||||
$command = "docker service logs -t {$this->container}";
|
||||
} else {
|
||||
$command = "docker logs -t {$this->container}";
|
||||
}
|
||||
} else {
|
||||
if ($this->server->isSwarm()) {
|
||||
$command = "docker service logs {$this->container}";
|
||||
} else {
|
||||
$command = "docker logs {$this->container}";
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->server->isNonRoot()) {
|
||||
$command = parseCommandsByLineForSudo(collect($command), $this->server);
|
||||
$command = $command[0];
|
||||
}
|
||||
|
||||
$sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
|
||||
|
||||
// Use array accumulation + implode for O(n) instead of O(n²) string concatenation
|
||||
// Enforce 50MB size limit to prevent memory exhaustion from large logs
|
||||
$logChunks = [];
|
||||
$accumulatedBytes = 0;
|
||||
$truncated = false;
|
||||
|
||||
Process::timeout(config('constants.ssh.command_timeout'))->run($sshCommand, function (string $type, string $output) use (&$logChunks, &$accumulatedBytes, &$truncated) {
|
||||
if ($truncated) {
|
||||
return;
|
||||
}
|
||||
|
||||
$output = removeAnsiColors($output);
|
||||
$outputBytes = strlen($output);
|
||||
|
||||
if ($accumulatedBytes + $outputBytes > self::MAX_DOWNLOAD_SIZE_BYTES) {
|
||||
$remaining = self::MAX_DOWNLOAD_SIZE_BYTES - $accumulatedBytes;
|
||||
if ($remaining > 0) {
|
||||
$logChunks[] = substr($output, 0, $remaining);
|
||||
}
|
||||
$truncated = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$logChunks[] = $output;
|
||||
$accumulatedBytes += $outputBytes;
|
||||
});
|
||||
|
||||
$allLogs = implode('', $logChunks);
|
||||
|
||||
if ($truncated) {
|
||||
$allLogs .= "\n\n[... Output truncated at 50MB limit ...]";
|
||||
}
|
||||
|
||||
if ($this->showTimeStamps) {
|
||||
$allLogs = str($allLogs)->split('/\n/')->sort(function ($a, $b) {
|
||||
$a = explode(' ', $a);
|
||||
$b = explode(' ', $b);
|
||||
|
||||
return $a[0] <=> $b[0];
|
||||
})->join("\n");
|
||||
}
|
||||
|
||||
return sanitizeLogsForExport($allLogs);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.get-logs');
|
||||
|
||||
@@ -16,19 +16,25 @@ class HealthChecks extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $healthCheckEnabled = false;
|
||||
|
||||
#[Validate(['string'])]
|
||||
#[Validate(['string', 'in:http,cmd'])]
|
||||
public string $healthCheckType = 'http';
|
||||
|
||||
#[Validate(['nullable', 'required_if:healthCheckType,cmd', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'])]
|
||||
public ?string $healthCheckCommand = null;
|
||||
|
||||
#[Validate(['required', 'string', 'in:GET,HEAD,POST,OPTIONS'])]
|
||||
public string $healthCheckMethod;
|
||||
|
||||
#[Validate(['string'])]
|
||||
#[Validate(['required', 'string', 'in:http,https'])]
|
||||
public string $healthCheckScheme;
|
||||
|
||||
#[Validate(['string'])]
|
||||
#[Validate(['required', 'string', 'regex:/^[a-zA-Z0-9.\-_]+$/'])]
|
||||
public string $healthCheckHost;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
#[Validate(['nullable', 'integer', 'min:1', 'max:65535'])]
|
||||
public ?string $healthCheckPort = null;
|
||||
|
||||
#[Validate(['string'])]
|
||||
#[Validate(['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%]+$#'])]
|
||||
public string $healthCheckPath;
|
||||
|
||||
#[Validate(['integer'])]
|
||||
@@ -54,12 +60,14 @@ class HealthChecks extends Component
|
||||
|
||||
protected $rules = [
|
||||
'healthCheckEnabled' => 'boolean',
|
||||
'healthCheckPath' => 'string',
|
||||
'healthCheckPort' => 'nullable|string',
|
||||
'healthCheckHost' => 'string',
|
||||
'healthCheckMethod' => 'string',
|
||||
'healthCheckType' => 'string|in:http,cmd',
|
||||
'healthCheckCommand' => ['nullable', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'],
|
||||
'healthCheckPath' => ['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%]+$#'],
|
||||
'healthCheckPort' => 'nullable|integer|min:1|max:65535',
|
||||
'healthCheckHost' => ['required', 'string', 'regex:/^[a-zA-Z0-9.\-_]+$/'],
|
||||
'healthCheckMethod' => 'required|string|in:GET,HEAD,POST,OPTIONS',
|
||||
'healthCheckReturnCode' => 'integer',
|
||||
'healthCheckScheme' => 'string',
|
||||
'healthCheckScheme' => 'required|string|in:http,https',
|
||||
'healthCheckResponseText' => 'nullable|string',
|
||||
'healthCheckInterval' => 'integer|min:1',
|
||||
'healthCheckTimeout' => 'integer|min:1',
|
||||
@@ -81,6 +89,8 @@ class HealthChecks extends Component
|
||||
|
||||
// Sync to model
|
||||
$this->resource->health_check_enabled = $this->healthCheckEnabled;
|
||||
$this->resource->health_check_type = $this->healthCheckType;
|
||||
$this->resource->health_check_command = $this->healthCheckCommand;
|
||||
$this->resource->health_check_method = $this->healthCheckMethod;
|
||||
$this->resource->health_check_scheme = $this->healthCheckScheme;
|
||||
$this->resource->health_check_host = $this->healthCheckHost;
|
||||
@@ -98,6 +108,8 @@ class HealthChecks extends Component
|
||||
} else {
|
||||
// Sync from model
|
||||
$this->healthCheckEnabled = $this->resource->health_check_enabled;
|
||||
$this->healthCheckType = $this->resource->health_check_type ?? 'http';
|
||||
$this->healthCheckCommand = $this->resource->health_check_command;
|
||||
$this->healthCheckMethod = $this->resource->health_check_method;
|
||||
$this->healthCheckScheme = $this->resource->health_check_scheme;
|
||||
$this->healthCheckHost = $this->resource->health_check_host;
|
||||
@@ -116,9 +128,12 @@ class HealthChecks extends Component
|
||||
public function instantSave()
|
||||
{
|
||||
$this->authorize('update', $this->resource);
|
||||
$this->validate();
|
||||
|
||||
// Sync component properties to model
|
||||
$this->resource->health_check_enabled = $this->healthCheckEnabled;
|
||||
$this->resource->health_check_type = $this->healthCheckType;
|
||||
$this->resource->health_check_command = $this->healthCheckCommand;
|
||||
$this->resource->health_check_method = $this->healthCheckMethod;
|
||||
$this->resource->health_check_scheme = $this->healthCheckScheme;
|
||||
$this->resource->health_check_host = $this->healthCheckHost;
|
||||
@@ -143,6 +158,8 @@ class HealthChecks extends Component
|
||||
|
||||
// Sync component properties to model
|
||||
$this->resource->health_check_enabled = $this->healthCheckEnabled;
|
||||
$this->resource->health_check_type = $this->healthCheckType;
|
||||
$this->resource->health_check_command = $this->healthCheckCommand;
|
||||
$this->resource->health_check_method = $this->healthCheckMethod;
|
||||
$this->resource->health_check_scheme = $this->healthCheckScheme;
|
||||
$this->resource->health_check_host = $this->healthCheckHost;
|
||||
@@ -171,6 +188,8 @@ class HealthChecks extends Component
|
||||
|
||||
// Sync component properties to model
|
||||
$this->resource->health_check_enabled = $this->healthCheckEnabled;
|
||||
$this->resource->health_check_type = $this->healthCheckType;
|
||||
$this->resource->health_check_command = $this->healthCheckCommand;
|
||||
$this->resource->health_check_method = $this->healthCheckMethod;
|
||||
$this->resource->health_check_scheme = $this->healthCheckScheme;
|
||||
$this->resource->health_check_host = $this->healthCheckHost;
|
||||
|
||||
@@ -49,9 +49,10 @@ class ResourceOperations extends Component
|
||||
{
|
||||
$this->authorize('update', $this->resource);
|
||||
|
||||
$new_destination = StandaloneDocker::find($destination_id);
|
||||
$teamScope = fn ($q) => $q->where('team_id', currentTeam()->id);
|
||||
$new_destination = StandaloneDocker::whereHas('server', $teamScope)->find($destination_id);
|
||||
if (! $new_destination) {
|
||||
$new_destination = SwarmDocker::find($destination_id);
|
||||
$new_destination = SwarmDocker::whereHas('server', $teamScope)->find($destination_id);
|
||||
}
|
||||
if (! $new_destination) {
|
||||
return $this->addError('destination_id', 'Destination not found.');
|
||||
@@ -352,7 +353,7 @@ class ResourceOperations extends Component
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->resource);
|
||||
$new_environment = Environment::findOrFail($environment_id);
|
||||
$new_environment = Environment::ownedByCurrentTeam()->findOrFail($environment_id);
|
||||
$this->resource->update([
|
||||
'environment_id' => $environment_id,
|
||||
]);
|
||||
|
||||
Reference in new issue
Block a user