mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
Merge remote-tracking branch 'origin/main' into next
This commit is contained in:
@@ -257,10 +257,8 @@ class Init extends Command
|
||||
$database = StandalonePostgresql::withTrashed()->find(0);
|
||||
if ($database && $database->trashed()) {
|
||||
$database->restore();
|
||||
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
||||
if (! $scheduledBackup) {
|
||||
if (! $database->scheduledBackups()->exists()) {
|
||||
ScheduledDatabaseBackup::create([
|
||||
'id' => 0,
|
||||
'enabled' => true,
|
||||
'save_s3' => false,
|
||||
'frequency' => '0 0 * * *',
|
||||
|
||||
@@ -71,7 +71,7 @@ class SettingsBackup extends Component
|
||||
$this->backup->enabled = false;
|
||||
$this->backup->save();
|
||||
}
|
||||
$this->executions = $this->backup->executions;
|
||||
$this->executions = $this->backup?->executions ?? [];
|
||||
}
|
||||
$this->settings = $settings;
|
||||
$this->s3s = $s3s;
|
||||
@@ -81,44 +81,46 @@ class SettingsBackup extends Component
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->settings);
|
||||
$server = Server::findOrFail(0);
|
||||
$out = instant_remote_process(['docker inspect coolify-db'], $server);
|
||||
$envs = format_docker_envs_to_json($out);
|
||||
$postgres_password = $envs['POSTGRES_PASSWORD'];
|
||||
$postgres_user = $envs['POSTGRES_USER'];
|
||||
$postgres_db = $envs['POSTGRES_DB'];
|
||||
$this->database = new StandalonePostgresql;
|
||||
$this->database->forceFill([
|
||||
'id' => 0,
|
||||
'name' => 'coolify-db',
|
||||
'description' => 'Coolify database',
|
||||
'postgres_user' => $postgres_user,
|
||||
'postgres_password' => $postgres_password,
|
||||
'postgres_db' => $postgres_db,
|
||||
'status' => 'running',
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
'destination_id' => 0,
|
||||
]);
|
||||
$this->database->save();
|
||||
$this->backup = ScheduledDatabaseBackup::create([
|
||||
'id' => 0,
|
||||
'enabled' => true,
|
||||
'save_s3' => false,
|
||||
'frequency' => '0 0 * * *',
|
||||
'database_id' => $this->database->id,
|
||||
'database_type' => StandalonePostgresql::class,
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->database = StandalonePostgresql::whereName('coolify-db')->first();
|
||||
if (! $this->database) {
|
||||
$server = Server::findOrFail(0);
|
||||
$out = instant_remote_process(['docker inspect coolify-db'], $server);
|
||||
$envs = format_docker_envs_to_json($out);
|
||||
$this->database = new StandalonePostgresql;
|
||||
$this->database->forceFill([
|
||||
'id' => 0,
|
||||
'name' => 'coolify-db',
|
||||
'description' => 'Coolify database',
|
||||
'postgres_user' => $envs['POSTGRES_USER'],
|
||||
'postgres_password' => $envs['POSTGRES_PASSWORD'],
|
||||
'postgres_db' => $envs['POSTGRES_DB'],
|
||||
'status' => 'running',
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
'destination_id' => 0,
|
||||
]);
|
||||
$this->database->save();
|
||||
}
|
||||
$this->backup = $this->database->scheduledBackups()->first()
|
||||
?? ScheduledDatabaseBackup::create([
|
||||
'enabled' => true,
|
||||
'save_s3' => false,
|
||||
'frequency' => '0 0 * * *',
|
||||
'database_id' => $this->database->id,
|
||||
'database_type' => StandalonePostgresql::class,
|
||||
'team_id' => 0,
|
||||
]);
|
||||
$this->database->refresh();
|
||||
$this->backup->refresh();
|
||||
auditLog('ui.database.backup_schedule_created', [
|
||||
'team_id' => $this->backup->team_id,
|
||||
'database_uuid' => $this->database->uuid,
|
||||
'database_name' => $this->database->name,
|
||||
'backup_uuid' => $this->backup->uuid,
|
||||
'frequency' => $this->backup->frequency,
|
||||
'save_s3' => (bool) $this->backup->save_s3,
|
||||
]);
|
||||
if ($this->backup->wasRecentlyCreated) {
|
||||
auditLog('ui.database.backup_schedule_created', [
|
||||
'team_id' => $this->backup->team_id,
|
||||
'database_uuid' => $this->database->uuid,
|
||||
'database_name' => $this->database->name,
|
||||
'backup_uuid' => $this->backup->uuid,
|
||||
'frequency' => $this->backup->frequency,
|
||||
'save_s3' => (bool) $this->backup->save_s3,
|
||||
]);
|
||||
}
|
||||
$this->s3s = S3Storage::whereTeamId(0)->get();
|
||||
|
||||
$this->uuid = $this->database->uuid;
|
||||
|
||||
@@ -12,15 +12,22 @@ export function initializeCopyButtonComponent() {
|
||||
await navigator.clipboard.writeText(value);
|
||||
} else {
|
||||
// Deprecated, but the only copy path on plain http (non-secure contexts).
|
||||
// The textarea goes next to the button, not into <body>: a modal's x-trap
|
||||
// pulls focus back from nodes outside it, which clears the selection.
|
||||
const previousFocus = document.activeElement;
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = value;
|
||||
textarea.setAttribute('readonly', '');
|
||||
textarea.setAttribute('tabindex', '-1');
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.left = '-9999px';
|
||||
document.body.appendChild(textarea);
|
||||
this.$el.after(textarea);
|
||||
textarea.focus({ preventScroll: true });
|
||||
textarea.select();
|
||||
textarea.setSelectionRange(0, textarea.value.length);
|
||||
const ok = document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
textarea.remove();
|
||||
previousFocus?.focus?.({ preventScroll: true });
|
||||
if (!ok) {
|
||||
throw new Error('Copy command was rejected.');
|
||||
}
|
||||
|
||||
@@ -223,39 +223,15 @@
|
||||
</div>
|
||||
|
||||
@if (filled($dnsCopyText))
|
||||
<div class="flex flex-wrap items-center justify-between gap-2"
|
||||
x-data="{
|
||||
copied: false,
|
||||
async copyAll(text) {
|
||||
try {
|
||||
if (navigator.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
const el = document.createElement('textarea');
|
||||
el.value = text;
|
||||
el.setAttribute('readonly', '');
|
||||
el.style.position = 'fixed';
|
||||
el.style.left = '-9999px';
|
||||
document.body.appendChild(el);
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
}
|
||||
this.copied = true;
|
||||
setTimeout(() => this.copied = false, 1000);
|
||||
} catch (e) {
|
||||
console.error('Copy failed', e);
|
||||
}
|
||||
}
|
||||
}">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<p class="text-[12px] text-neutral-500 dark:text-fg-dim">
|
||||
{{ count($dnsHints) }}
|
||||
{{ count($dnsHints) === 1 ? 'entry' : 'entries' }}
|
||||
· BIND zone format
|
||||
</p>
|
||||
<button type="button" class="button shrink-0"
|
||||
title="Copy as BIND-compatible zone file"
|
||||
@click.prevent="copyAll(@js($dnsCopyText))">
|
||||
title="Copy as BIND-compatible zone file" x-data="copyButton"
|
||||
@click.prevent="copy(@js($dnsCopyText))">
|
||||
<span x-text="copied ? 'Copied' : 'Copy all'"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
use App\Livewire\SettingsBackup;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Once;
|
||||
use Livewire\Livewire;
|
||||
|
||||
@@ -84,3 +88,76 @@ test('instance backup settings show a configure button when backup is not set up
|
||||
->assertSee('Configure backup')
|
||||
->assertSeeHtml('wire:click="addCoolifyDatabase"');
|
||||
});
|
||||
|
||||
function settingsBackupInstanceDatabaseWithoutSchedule(): StandalonePostgresql
|
||||
{
|
||||
Server::flushIdentityMap();
|
||||
$rootTeam = Team::find(0) ?? Team::factory()->create(['id' => 0]);
|
||||
$server = Server::factory()->create([
|
||||
'id' => 0,
|
||||
'team_id' => $rootTeam->id,
|
||||
'ip' => '127.0.0.1',
|
||||
]);
|
||||
$server->settings()->update([
|
||||
'is_reachable' => true,
|
||||
'is_usable' => true,
|
||||
'force_disabled' => false,
|
||||
]);
|
||||
StandaloneDocker::query()->where('server_id', $server->id)->firstOrFail()->forceFill(['id' => 0])->save();
|
||||
InstanceSettings::forceCreate(['id' => 0]);
|
||||
Once::flush();
|
||||
|
||||
$database = new StandalonePostgresql;
|
||||
$database->forceFill([
|
||||
'id' => 0,
|
||||
'name' => 'coolify-db',
|
||||
'description' => 'Coolify database',
|
||||
'postgres_user' => 'coolify',
|
||||
'postgres_password' => 'password',
|
||||
'postgres_db' => 'coolify',
|
||||
'status' => 'running',
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
'destination_id' => 0,
|
||||
]);
|
||||
$database->save();
|
||||
|
||||
$user = User::factory()->create();
|
||||
$rootTeam->members()->attach($user->id, ['role' => 'admin']);
|
||||
test()->actingAs($user);
|
||||
session(['currentTeam' => ['id' => $rootTeam->id]]);
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
test('instance backup settings render when the instance database has no backup schedule', function () {
|
||||
settingsBackupInstanceDatabaseWithoutSchedule();
|
||||
|
||||
Livewire::test(SettingsBackup::class)
|
||||
->assertOk()
|
||||
->assertSet('executions', [])
|
||||
->assertSee('Backup is not configured')
|
||||
->assertSee('Configure backup');
|
||||
});
|
||||
|
||||
test('configure backup creates only the missing schedule for an existing instance database', function () {
|
||||
$database = settingsBackupInstanceDatabaseWithoutSchedule();
|
||||
$otherTeam = Team::factory()->create();
|
||||
auth()->user()->teams()->attach($otherTeam->id, ['role' => 'owner']);
|
||||
session(['currentTeam' => ['id' => $otherTeam->id]]);
|
||||
Process::fake();
|
||||
|
||||
Livewire::test(SettingsBackup::class)
|
||||
->call('addCoolifyDatabase')
|
||||
->assertHasNoErrors()
|
||||
->assertSee('Instance database');
|
||||
|
||||
Process::assertNothingRan();
|
||||
$backup = $database->scheduledBackups()->sole();
|
||||
|
||||
expect(StandalonePostgresql::query()->where('name', 'coolify-db')->count())->toBe(1)
|
||||
->and($backup->id)->not->toBe(0)
|
||||
->and($backup->team_id)->toBe(0)
|
||||
->and($backup->enabled)->toBeTruthy()
|
||||
->and($backup->frequency)->toBe('0 0 * * *')
|
||||
->and(ScheduledDatabaseBackup::query()->count())->toBe(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user