mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
Merge remote-tracking branch 'origin/main' into release/v4.5
This commit is contained in:
@@ -7,20 +7,32 @@ use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldQueue
|
||||
class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldBeUniqueUntilProcessing, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Only one sync per resource can wait in the queue. The compose parser dispatches
|
||||
* this job once per volume on every parse, so without this the queue fills with copies.
|
||||
*/
|
||||
public int $uniqueFor = 600;
|
||||
|
||||
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return $this->resource::class.':'.$this->resource->id;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->resource->getFilesFromServer(isInit: true);
|
||||
|
||||
@@ -178,11 +178,11 @@ class Domains extends Component
|
||||
$this->loadDomainState();
|
||||
}
|
||||
|
||||
public function refreshDomains(): void
|
||||
public function refreshDomains(bool $parseCompose = true): void
|
||||
{
|
||||
$editingRow = $this->editingIndex !== null ? ($this->domainRows[$this->editingIndex] ?? null) : null;
|
||||
|
||||
$this->loadDomainState();
|
||||
$this->loadDomainState($parseCompose);
|
||||
|
||||
if ($editingRow !== null) {
|
||||
$index = collect($this->domainRows)->search(fn (array $row): bool => $row['url'] === $editingRow['url']
|
||||
@@ -199,7 +199,9 @@ class Domains extends Component
|
||||
->where('dns_status', 'checking')
|
||||
->values();
|
||||
|
||||
$this->refreshDomains();
|
||||
// The poll only refreshes DNS results. Parsing the compose file here would
|
||||
// queue file sync jobs on every tick and starve the DNS check jobs.
|
||||
$this->refreshDomains(parseCompose: false);
|
||||
|
||||
foreach ($checkingRows as $checkingRow) {
|
||||
$row = collect($this->domainRows)->first(fn (array $row): bool => $row['url'] === $checkingRow['url']
|
||||
@@ -258,7 +260,7 @@ class Domains extends Component
|
||||
$this->dispatch('success', 'HTTP to HTTPS redirect updated.');
|
||||
}
|
||||
|
||||
public function loadDomainState(): void
|
||||
public function loadDomainState(bool $parseCompose = true): void
|
||||
{
|
||||
$this->application->refresh();
|
||||
$this->application->loadMissing(['destination.server', 'settings', 'additional_servers']);
|
||||
@@ -293,23 +295,27 @@ class Domains extends Component
|
||||
$this->serverIpConfigured = null;
|
||||
}
|
||||
|
||||
$this->composeServices = [];
|
||||
$pendingRedirect = $this->pendingRedirectService !== null
|
||||
? ($this->serviceRedirects[$this->serviceRedirectWireKey($this->pendingRedirectService)] ?? null)
|
||||
: null;
|
||||
$this->serviceRedirects = [];
|
||||
if ($this->isCompose) {
|
||||
if (! $this->isCompose) {
|
||||
$this->composeServices = [];
|
||||
} elseif ($parseCompose) {
|
||||
try {
|
||||
$parsed = $this->application->parse() ?? [];
|
||||
} catch (\Throwable) {
|
||||
$parsed = [];
|
||||
}
|
||||
$this->composeServices = [];
|
||||
$services = data_get($parsed, 'services', []);
|
||||
foreach ($services as $serviceName => $service) {
|
||||
if (! isDatabaseImage(data_get($service, 'image'))) {
|
||||
$this->composeServices[] = $serviceName;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->isCompose) {
|
||||
if ($this->newDomainService === null && count($this->composeServices) > 0) {
|
||||
$this->newDomainService = $this->composeServices[0];
|
||||
}
|
||||
|
||||
@@ -83,6 +83,23 @@ export function isFindShortcut(event) {
|
||||
return Boolean(event.ctrlKey || event.metaKey) && !event.altKey && !event.shiftKey && event.key?.toLowerCase() === 'f';
|
||||
}
|
||||
|
||||
const DEFAULT_LOG_FILTERS = { error: true, warning: true, debug: true, info: true };
|
||||
|
||||
/** Read saved level filters. Invalid saved values fall back to the defaults. */
|
||||
export function readLogFilters(saved) {
|
||||
const filters = { ...DEFAULT_LOG_FILTERS };
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(saved);
|
||||
} catch {
|
||||
return filters;
|
||||
}
|
||||
for (const level of Object.keys(filters)) {
|
||||
if (typeof parsed?.[level] === 'boolean') filters[level] = parsed[level];
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
export function filterLogLines(lines, filters, query) {
|
||||
const needle = query.trim().toLowerCase();
|
||||
return lines.filter((line) => filters[line.level] !== false && (!needle || line.lower.includes(needle)));
|
||||
@@ -170,7 +187,7 @@ export function initializeRuntimeLogsComponent() {
|
||||
scrollDebounce: null,
|
||||
destroyed: false,
|
||||
colorLogs: localStorage.getItem('coolify-color-logs') === 'true',
|
||||
logFilters: JSON.parse(localStorage.getItem('coolify-log-filters')) || { error: true, warning: true, debug: true, info: true },
|
||||
logFilters: readLogFilters(localStorage.getItem('coolify-log-filters')),
|
||||
searchQuery: '',
|
||||
appliedQuery: '',
|
||||
matchCount: 0,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use App\Jobs\CheckDomainDnsJob;
|
||||
use App\Jobs\ConfigureDnsRecordJob;
|
||||
use App\Jobs\ServerFilesFromServerJob;
|
||||
use App\Livewire\Project\Application\Domains;
|
||||
use App\Livewire\Project\Application\PreviewDomains;
|
||||
use App\Livewire\Project\Application\Previews;
|
||||
@@ -3656,3 +3657,25 @@ it('offers dns provider record creation for a public server ip', function () {
|
||||
->toContain('Add with Cloudflare')
|
||||
->not->toContain('The server has no public IP address; add the DNS record manually.');
|
||||
});
|
||||
|
||||
it('does not queue file sync jobs when polling dns checks for compose applications', function () {
|
||||
Queue::fake();
|
||||
|
||||
$this->application->update([
|
||||
'build_pack' => 'dockercompose',
|
||||
'docker_compose_raw' => "services:\n web:\n image: nginx:alpine\n volumes:\n - web-data:/data\n api:\n image: nginx:alpine\n volumes:\n - api-data:/data\n - api-conf:/etc/api\nvolumes:\n web-data:\n api-data:\n api-conf:\n",
|
||||
'docker_compose_domains' => json_encode([
|
||||
'web' => ['domain' => 'https://compose-poll-test.invalid'],
|
||||
]),
|
||||
'fqdn' => null,
|
||||
]);
|
||||
|
||||
$component = Livewire::test(Domains::class, ['application' => $this->application->fresh()]);
|
||||
$queuedOnMount = Queue::pushed(ServerFilesFromServerJob::class)->count();
|
||||
|
||||
$component->call('pollDnsChecks')->call('pollDnsChecks')->call('pollDnsChecks');
|
||||
|
||||
expect($queuedOnMount)->toBe(1)
|
||||
->and(Queue::pushed(ServerFilesFromServerJob::class))->toHaveCount(1)
|
||||
->and($component->get('composeServices'))->toBe(['web', 'api']);
|
||||
});
|
||||
|
||||
@@ -124,3 +124,23 @@ it('streams only new lines and appends them to the loaded log', function () {
|
||||
|
||||
$page->screenshot(filename: 'runtime-logs-virtualized-stream');
|
||||
});
|
||||
|
||||
it('falls back to the default level filters when the saved filters are invalid', function () {
|
||||
$page = visit('/login')
|
||||
->fill('email', 'test@example.com')
|
||||
->fill('password', 'password')
|
||||
->click('Login')
|
||||
->assertSee('Dashboard');
|
||||
|
||||
$page->script('localStorage.setItem("coolify-log-filters", "{")');
|
||||
$page->navigate("/server/{$this->stack['server']->uuid}/sentinel/logs")
|
||||
->assertSee('log line 00001');
|
||||
|
||||
// A valid saved preference is still applied.
|
||||
$page->script('localStorage.setItem("coolify-log-filters", JSON.stringify({ info: false }))');
|
||||
$page->navigate("/server/{$this->stack['server']->uuid}/sentinel/logs")
|
||||
->wait(1)
|
||||
->assertDontSee('log line 00001');
|
||||
|
||||
$page->screenshot(filename: 'runtime-logs-invalid-saved-filters');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user