mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-26 09:20:54 -04:00
Merge branch 'next' into shadow/add-shared-server-env
This commit is contained in:
@@ -131,10 +131,10 @@ function sharedDataApplications()
|
||||
'manual_webhook_secret_gitea' => 'string|nullable',
|
||||
'docker_compose_location' => 'string',
|
||||
'docker_compose' => 'string|nullable',
|
||||
'docker_compose_raw' => 'string|nullable',
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
'docker_compose_custom_start_command' => 'string|nullable',
|
||||
'docker_compose_custom_build_command' => 'string|nullable',
|
||||
'is_container_label_escape_enabled' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -179,4 +179,6 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
|
||||
$request->offsetUnset('is_static');
|
||||
$request->offsetUnset('force_domain_override');
|
||||
$request->offsetUnset('autogenerate_domain');
|
||||
$request->offsetUnset('is_container_label_escape_enabled');
|
||||
$request->offsetUnset('docker_compose_raw');
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ const SUPPORTED_OS = [
|
||||
|
||||
const NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK = [
|
||||
'pgadmin',
|
||||
'postgresus',
|
||||
'databasus',
|
||||
'redis-insight',
|
||||
];
|
||||
const NEEDS_TO_DISABLE_GZIP = [
|
||||
|
||||
@@ -129,8 +129,8 @@ function format_docker_envs_to_json($rawOutput)
|
||||
}
|
||||
function checkMinimumDockerEngineVersion($dockerVersion)
|
||||
{
|
||||
$majorDockerVersion = str($dockerVersion)->before('.')->value();
|
||||
$requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.')->value();
|
||||
$majorDockerVersion = (int) str($dockerVersion)->before('.')->value();
|
||||
$requiredDockerVersion = (int) str(config('constants.docker.minimum_required_version'))->before('.')->value();
|
||||
if ($majorDockerVersion < $requiredDockerVersion) {
|
||||
$dockerVersion = null;
|
||||
}
|
||||
|
||||
@@ -162,3 +162,54 @@ function loadRepositoryByPage(GithubApp $source, string $token, int $page)
|
||||
'repositories' => $json['repositories'],
|
||||
];
|
||||
}
|
||||
function getGithubCommitRangeFiles(?GithubApp $source, string $owner, string $repo, string $beforeSha, string $afterSha): array
|
||||
{
|
||||
try {
|
||||
if (! $source) {
|
||||
// Manual webhooks don't have GitHub App authentication
|
||||
// Return empty array so watch paths are ignored (current behavior)
|
||||
return [];
|
||||
}
|
||||
|
||||
$endpoint = "/repos/{$owner}/{$repo}/compare/{$beforeSha}...{$afterSha}";
|
||||
$response = githubApi($source, $endpoint, 'get', null, false);
|
||||
|
||||
if (! $response) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = collect(data_get($response, 'data.files', []));
|
||||
|
||||
return $files->pluck('filename')->filter()->values()->toArray();
|
||||
} catch (Exception $e) {
|
||||
ray('Error fetching GitHub commit range files: '.$e->getMessage());
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getGithubPullRequestFiles(?GithubApp $source, string $owner, string $repo, int $pullRequestId): array
|
||||
{
|
||||
try {
|
||||
if (! $source) {
|
||||
// Manual webhooks don't have GitHub App authentication
|
||||
// Return empty array so watch paths are ignored (current behavior)
|
||||
return [];
|
||||
}
|
||||
|
||||
$endpoint = "/repos/{$owner}/{$repo}/pulls/{$pullRequestId}/files";
|
||||
$response = githubApi($source, $endpoint, 'get', null, false);
|
||||
|
||||
if (! $response) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = collect(data_get($response, 'data', []));
|
||||
|
||||
return $files->pluck('filename')->filter()->values()->toArray();
|
||||
} catch (Exception $e) {
|
||||
ray('Error fetching GitHub PR files: '.$e->getMessage());
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ function excludeCertainErrors(string $errorOutput, ?int $exitCode = null)
|
||||
throw new \RuntimeException($errorMessage, $exitCode);
|
||||
}
|
||||
|
||||
function decode_remote_command_output(?ApplicationDeploymentQueue $application_deployment_queue = null): Collection
|
||||
function decode_remote_command_output(?ApplicationDeploymentQueue $application_deployment_queue = null, bool $includeAll = false): Collection
|
||||
{
|
||||
if (is_null($application_deployment_queue)) {
|
||||
return collect([]);
|
||||
@@ -216,7 +216,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
||||
|
||||
$seenCommands = collect();
|
||||
$formatted = collect($decoded);
|
||||
if (! $is_debug_enabled) {
|
||||
if (! $is_debug_enabled && ! $includeAll) {
|
||||
$formatted = $formatted->filter(fn ($i) => $i['hidden'] === false ?? false);
|
||||
}
|
||||
|
||||
|
||||
@@ -182,8 +182,11 @@ function refreshSession(?Team $team = null): void
|
||||
$team = User::find(Auth::id())->teams->first();
|
||||
}
|
||||
}
|
||||
// Clear old cache key format for backwards compatibility
|
||||
Cache::forget('team:'.Auth::id());
|
||||
Cache::remember('team:'.Auth::id(), 3600, function () use ($team) {
|
||||
// Use new cache key format that includes team ID
|
||||
Cache::forget('user:'.Auth::id().':team:'.$team->id);
|
||||
Cache::remember('user:'.Auth::id().':team:'.$team->id, 3600, function () use ($team) {
|
||||
return $team;
|
||||
});
|
||||
session(['currentTeam' => $team]);
|
||||
@@ -384,7 +387,7 @@ function base_url(bool $withPort = true): string
|
||||
|
||||
function isSubscribed()
|
||||
{
|
||||
return isSubscriptionActive() || auth()->user()->isInstanceAdmin();
|
||||
return isSubscriptionActive();
|
||||
}
|
||||
|
||||
function isProduction(): bool
|
||||
@@ -548,7 +551,21 @@ function getResourceByUuid(string $uuid, ?int $teamId = null)
|
||||
return null;
|
||||
}
|
||||
$resource = queryResourcesByUuid($uuid);
|
||||
if (! is_null($resource) && $resource->environment->project->team_id === $teamId) {
|
||||
if (is_null($resource)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ServiceDatabase has a different relationship path: service->environment->project->team_id
|
||||
if ($resource instanceof \App\Models\ServiceDatabase) {
|
||||
if ($resource->service?->environment?->project?->team_id === $teamId) {
|
||||
return $resource;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Standard resources: environment->project->team_id
|
||||
if ($resource->environment->project->team_id === $teamId) {
|
||||
return $resource;
|
||||
}
|
||||
|
||||
@@ -635,6 +652,12 @@ function queryResourcesByUuid(string $uuid)
|
||||
return $clickhouse;
|
||||
}
|
||||
|
||||
// Check for ServiceDatabase by its own UUID
|
||||
$serviceDatabase = ServiceDatabase::whereUuid($uuid)->first();
|
||||
if ($serviceDatabase) {
|
||||
return $serviceDatabase;
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
function generateTagDeployWebhook($tag_name)
|
||||
@@ -966,6 +989,9 @@ function generateEnvValue(string $command, Service|Application|null $service = n
|
||||
case 'USER':
|
||||
$generatedValue = Str::random(16);
|
||||
break;
|
||||
case 'LOWERCASEUSER':
|
||||
$generatedValue = Str::lower(Str::random(16));
|
||||
break;
|
||||
case 'SUPABASEANON':
|
||||
$signingKey = $service->environment_variables()->where('key', 'SERVICE_PASSWORD_JWT')->first();
|
||||
if (is_null($signingKey)) {
|
||||
@@ -1164,7 +1190,7 @@ function get_public_ips()
|
||||
$ipv4 = $first->output();
|
||||
if ($ipv4) {
|
||||
$ipv4 = trim($ipv4);
|
||||
$validate_ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP);
|
||||
$validate_ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
||||
if ($validate_ipv4 == false) {
|
||||
echo "Invalid ipv4: $ipv4\n";
|
||||
|
||||
@@ -1179,7 +1205,7 @@ function get_public_ips()
|
||||
$ipv6 = $second->output();
|
||||
if ($ipv6) {
|
||||
$ipv6 = trim($ipv6);
|
||||
$validate_ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP);
|
||||
$validate_ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
|
||||
if ($validate_ipv6 == false) {
|
||||
echo "Invalid ipv6: $ipv6\n";
|
||||
|
||||
@@ -2934,6 +2960,23 @@ function wireNavigate(): string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to a named route with SPA navigation support.
|
||||
* Automatically uses wire:navigate when is_wire_navigate_enabled is true.
|
||||
*/
|
||||
function redirectRoute(Livewire\Component $component, string $name, array $parameters = []): mixed
|
||||
{
|
||||
$navigate = true;
|
||||
|
||||
try {
|
||||
$navigate = instanceSettings()->is_wire_navigate_enabled ?? true;
|
||||
} catch (\Exception $e) {
|
||||
$navigate = true;
|
||||
}
|
||||
|
||||
return $component->redirectRoute($name, $parameters, navigate: $navigate);
|
||||
}
|
||||
|
||||
function getHelperVersion(): string
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
@@ -3399,3 +3442,81 @@ function verifyPasswordConfirmation(mixed $password, ?Livewire\Component $compon
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downsample metrics using the Largest-Triangle-Three-Buckets (LTTB) algorithm.
|
||||
* This preserves the visual shape of the data better than simple averaging.
|
||||
*
|
||||
* @param array $data Array of [timestamp, value] pairs
|
||||
* @param int $threshold Target number of points
|
||||
* @return array Downsampled data
|
||||
*/
|
||||
function downsampleLTTB(array $data, int $threshold): array
|
||||
{
|
||||
$dataLength = count($data);
|
||||
|
||||
// Return unchanged if threshold >= data length, or if threshold <= 2
|
||||
// (threshold <= 2 would cause division by zero in bucket calculation)
|
||||
if ($threshold >= $dataLength || $threshold <= 2) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$sampled = [];
|
||||
$sampled[] = $data[0]; // Always keep first point
|
||||
|
||||
$bucketSize = ($dataLength - 2) / ($threshold - 2);
|
||||
|
||||
$a = 0; // Index of previous selected point
|
||||
|
||||
for ($i = 0; $i < $threshold - 2; $i++) {
|
||||
// Calculate bucket range
|
||||
$bucketStart = (int) floor(($i + 1) * $bucketSize) + 1;
|
||||
$bucketEnd = (int) floor(($i + 2) * $bucketSize) + 1;
|
||||
$bucketEnd = min($bucketEnd, $dataLength - 1);
|
||||
|
||||
// Calculate average point for next bucket (used as reference)
|
||||
$nextBucketStart = (int) floor(($i + 2) * $bucketSize) + 1;
|
||||
$nextBucketEnd = (int) floor(($i + 3) * $bucketSize) + 1;
|
||||
$nextBucketEnd = min($nextBucketEnd, $dataLength - 1);
|
||||
|
||||
$avgX = 0;
|
||||
$avgY = 0;
|
||||
$nextBucketCount = $nextBucketEnd - $nextBucketStart + 1;
|
||||
|
||||
if ($nextBucketCount > 0) {
|
||||
for ($j = $nextBucketStart; $j <= $nextBucketEnd; $j++) {
|
||||
$avgX += $data[$j][0];
|
||||
$avgY += $data[$j][1];
|
||||
}
|
||||
$avgX /= $nextBucketCount;
|
||||
$avgY /= $nextBucketCount;
|
||||
}
|
||||
|
||||
// Find point in current bucket with largest triangle area
|
||||
$maxArea = -1;
|
||||
$maxAreaIndex = $bucketStart;
|
||||
|
||||
$pointAX = $data[$a][0];
|
||||
$pointAY = $data[$a][1];
|
||||
|
||||
for ($j = $bucketStart; $j <= $bucketEnd; $j++) {
|
||||
// Triangle area calculation
|
||||
$area = abs(
|
||||
($pointAX - $avgX) * ($data[$j][1] - $pointAY) -
|
||||
($pointAX - $data[$j][0]) * ($avgY - $pointAY)
|
||||
) * 0.5;
|
||||
|
||||
if ($area > $maxArea) {
|
||||
$maxArea = $area;
|
||||
$maxAreaIndex = $j;
|
||||
}
|
||||
}
|
||||
|
||||
$sampled[] = $data[$maxAreaIndex];
|
||||
$a = $maxAreaIndex;
|
||||
}
|
||||
|
||||
$sampled[] = $data[$dataLength - 1]; // Always keep last point
|
||||
|
||||
return $sampled;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ function isSubscriptionActive()
|
||||
if (! $team) {
|
||||
return false;
|
||||
}
|
||||
// Root team (id=0) doesn't require subscription
|
||||
if ($team->id === 0) {
|
||||
return true;
|
||||
}
|
||||
$subscription = $team?->subscription;
|
||||
|
||||
if (is_null($subscription)) {
|
||||
|
||||
Reference in New Issue
Block a user