Merge branch 'next' into new-magic

This commit is contained in:
🏔️ Peak 2024-11-04 17:58:44 +01:00 committed by GitHub
commit a6567d4685
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 835 additions and 495 deletions

View file

@ -2,7 +2,7 @@
namespace App\Actions\Database;
use App\Events\DatabaseStatusChanged;
use App\Events\DatabaseProxyStopped;
use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
@ -27,7 +27,11 @@ class StopDatabaseProxy
$server = data_get($database, 'service.server');
}
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
$database->is_public = false;
$database->save();
DatabaseStatusChanged::dispatch();
DatabaseProxyStopped::dispatch();
}
}

View 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;
use Illuminate\Support\Facades\Auth;
class DatabaseProxyStopped implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct($teamId = null)
{
if (is_null($teamId)) {
$teamId = Auth::user()->currentTeam()->id ?? null;
}
if (is_null($teamId)) {
throw new \Exception('Team id is null');
}
$this->teamId = $teamId;
}
public function broadcastOn(): array
{
return [
new PrivateChannel("team.{$this->teamId}"),
];
}
}

View file

@ -7,27 +7,29 @@ use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Auth;
class DatabaseStatusChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public ?string $userId = null;
public $userId = null;
public function __construct($userId = null)
{
if (is_null($userId)) {
$userId = auth()->user()->id ?? null;
$userId = Auth::id() ?? null;
}
if (is_null($userId)) {
return false;
}
$this->userId = $userId;
}
public function broadcastOn(): ?array
{
if ($this->userId) {
if (! is_null($this->userId)) {
return [
new PrivateChannel("user.{$this->userId}"),
];

View file

@ -7,6 +7,7 @@ use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Auth;
class ServiceStatusChanged implements ShouldBroadcast
{
@ -17,7 +18,7 @@ class ServiceStatusChanged implements ShouldBroadcast
public function __construct($userId = null)
{
if (is_null($userId)) {
$userId = auth()->user()->id ?? null;
$userId = Auth::id() ?? null;
}
if (is_null($userId)) {
return false;

View file

@ -131,7 +131,7 @@ class Controller extends BaseController
}
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
$invitation->delete();
if (auth()->user()?->id !== $user->id) {
if (Auth::id() !== $user->id) {
return redirect()->route('login');
}
refreshSession($invitation->team);
@ -146,10 +146,10 @@ class Controller extends BaseController
{
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail();
if (is_null(auth()->user())) {
if (is_null(Auth::user())) {
return redirect()->route('login');
}
if (auth()->user()->id !== $user->id) {
if (Auth::id() !== $user->id) {
abort(401);
}
$invitation->delete();

View file

@ -3,7 +3,9 @@
namespace App\Livewire\Admin;
use App\Models\User;
use Illuminate\Container\Attributes\Auth as AttributesAuth;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
@ -23,7 +25,7 @@ class Index extends Component
return redirect()->route('dashboard');
}
if (auth()->user()->id !== 0) {
if (Auth::id() !== 0) {
return redirect()->route('dashboard');
}
$this->getSubscribers();
@ -51,13 +53,13 @@ class Index extends Component
public function switchUser(int $user_id)
{
if (auth()->user()->id !== 0) {
if (AttributesAuth::id() !== 0) {
return redirect()->route('dashboard');
}
$user = User::find($user_id);
$team_to_switch_to = $user->teams->first();
Cache::forget("team:{$user->id}");
auth()->login($user);
Auth::login($user);
refreshSession($team_to_switch_to);
return redirect(request()->header('Referer'));

View file

@ -3,6 +3,7 @@
namespace App\Livewire;
use App\Models\InstanceSettings;
use Illuminate\Container\Attributes\Auth as AttributesAuth;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
@ -31,7 +32,7 @@ class NavbarDeleteTeam extends Component
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === auth()->user()->id) {
if ($user->id === AttributesAuth::id()) {
return;
}
$user->teams()->detach($currentTeam);

View file

@ -2,6 +2,7 @@
namespace App\Livewire\Profile;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
use Livewire\Attributes\Validate;
@ -24,9 +25,9 @@ class Index extends Component
public function mount()
{
$this->userId = auth()->user()->id;
$this->name = auth()->user()->name;
$this->email = auth()->user()->email;
$this->userId = Auth::id();
$this->name = Auth::user()->name;
$this->email = Auth::user()->email;
}
public function submit()
@ -35,7 +36,7 @@ class Index extends Component
$this->validate([
'name' => 'required',
]);
auth()->user()->update([
Auth::user()->update([
'name' => $this->name,
]);

View file

@ -3,6 +3,7 @@
namespace App\Livewire\Project\Application\Preview;
use App\Models\Application;
use Livewire\Attributes\Rule;
use Livewire\Component;
use Spatie\Url\Url;
@ -10,49 +11,53 @@ class Form extends Component
{
public Application $application;
public string $preview_url_template;
protected $rules = [
'application.preview_url_template' => 'required',
];
protected $validationAttributes = [
'application.preview_url_template' => 'preview url template',
];
public function resetToDefault()
{
$this->application->preview_url_template = '{{pr_id}}.{{domain}}';
$this->preview_url_template = $this->application->preview_url_template;
$this->application->save();
$this->generate_real_url();
}
public function generate_real_url()
{
if (data_get($this->application, 'fqdn')) {
try {
$firstFqdn = str($this->application->fqdn)->before(',');
$url = Url::fromString($firstFqdn);
$host = $url->getHost();
$this->preview_url_template = str($this->application->preview_url_template)->replace('{{domain}}', $host);
} catch (\Exception) {
$this->dispatch('error', 'Invalid FQDN.');
}
}
}
#[Rule('required')]
public string $previewUrlTemplate;
public function mount()
{
$this->generate_real_url();
try {
$this->previewUrlTemplate = $this->application->preview_url_template;
$this->generateRealUrl();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit()
{
$this->validate();
$this->application->preview_url_template = str_replace(' ', '', $this->application->preview_url_template);
$this->application->save();
$this->dispatch('success', 'Preview url template updated.');
$this->generate_real_url();
try {
$this->resetErrorBag();
$this->validate();
$this->application->preview_url_template = str_replace(' ', '', $this->previewUrlTemplate);
$this->application->save();
$this->dispatch('success', 'Preview url template updated.');
$this->generateRealUrl();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function resetToDefault()
{
try {
$this->application->preview_url_template = '{{pr_id}}.{{domain}}';
$this->previewUrlTemplate = $this->application->preview_url_template;
$this->application->save();
$this->generateRealUrl();
$this->dispatch('success', 'Preview url template updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function generateRealUrl()
{
if (data_get($this->application, 'fqdn')) {
$firstFqdn = str($this->application->fqdn)->before(',');
$url = Url::fromString($firstFqdn);
$host = $url->getHost();
$this->previewUrlTemplate = str($this->application->preview_url_template)->replace('{{domain}}', $host);
}
}
}

View file

@ -3,32 +3,55 @@
namespace App\Livewire\Project\Application;
use App\Models\Application;
use Livewire\Attributes\Rule;
use Livewire\Component;
class Swarm extends Component
{
public Application $application;
public string $swarm_placement_constraints = '';
#[Rule('required')]
public int $swarmReplicas;
protected $rules = [
'application.swarm_replicas' => 'required',
'application.swarm_placement_constraints' => 'nullable',
'application.settings.is_swarm_only_worker_nodes' => 'required',
];
#[Rule(['nullable'])]
public ?string $swarmPlacementConstraints = null;
#[Rule('required')]
public bool $isSwarmOnlyWorkerNodes;
public function mount()
{
if ($this->application->swarm_placement_constraints) {
$this->swarm_placement_constraints = base64_decode($this->application->swarm_placement_constraints);
try {
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->application->swarm_replicas = $this->swarmReplicas;
$this->application->swarm_placement_constraints = $this->swarmPlacementConstraints ? base64_encode($this->swarmPlacementConstraints) : null;
$this->application->settings->is_swarm_only_worker_nodes = $this->isSwarmOnlyWorkerNodes;
$this->application->save();
$this->application->settings->save();
} else {
$this->swarmReplicas = $this->application->swarm_replicas;
if ($this->application->swarm_placement_constraints) {
$this->swarmPlacementConstraints = base64_decode($this->application->swarm_placement_constraints);
} else {
$this->swarmPlacementConstraints = null;
}
$this->isSwarmOnlyWorkerNodes = $this->application->settings->is_swarm_only_worker_nodes;
}
}
public function instantSave()
{
try {
$this->validate();
$this->application->settings->save();
$this->syncData(true);
$this->dispatch('success', 'Swarm settings updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
@ -38,14 +61,7 @@ class Swarm extends Component
public function submit()
{
try {
$this->validate();
if ($this->swarm_placement_constraints) {
$this->application->swarm_placement_constraints = base64_encode($this->swarm_placement_constraints);
} else {
$this->application->swarm_placement_constraints = null;
}
$this->application->save();
$this->syncData(true);
$this->dispatch('success', 'Swarm settings updated.');
} catch (\Throwable $e) {
return handleError($e, $this);

View file

@ -4,56 +4,87 @@ namespace App\Livewire\Project\Database;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use Exception;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Rule;
use Livewire\Component;
use Spatie\Url\Url;
class BackupEdit extends Component
{
public ?ScheduledDatabaseBackup $backup;
public ScheduledDatabaseBackup $backup;
#[Locked]
public $s3s;
#[Locked]
public $parameters;
#[Rule(['required', 'boolean'])]
public bool $delete_associated_backups_locally = false;
#[Rule(['required', 'boolean'])]
public bool $delete_associated_backups_s3 = false;
#[Rule(['required', 'boolean'])]
public bool $delete_associated_backups_sftp = false;
#[Rule(['nullable', 'string'])]
public ?string $status = null;
public array $parameters;
#[Rule(['required', 'boolean'])]
public bool $backupEnabled = false;
protected $rules = [
'backup.enabled' => 'required|boolean',
'backup.frequency' => 'required|string',
'backup.number_of_backups_locally' => 'required|integer|min:1',
'backup.save_s3' => 'required|boolean',
'backup.s3_storage_id' => 'nullable|integer',
'backup.databases_to_backup' => 'nullable',
'backup.dump_all' => 'required|boolean',
];
#[Rule(['required', 'string'])]
public string $frequency = '';
protected $validationAttributes = [
'backup.enabled' => 'Enabled',
'backup.frequency' => 'Frequency',
'backup.number_of_backups_locally' => 'Number of Backups Locally',
'backup.save_s3' => 'Save to S3',
'backup.s3_storage_id' => 'S3 Storage',
'backup.databases_to_backup' => 'Databases to Backup',
'backup.dump_all' => 'Backup All Databases',
];
#[Rule(['required', 'integer', 'min:1'])]
public int $numberOfBackupsLocally = 1;
protected $messages = [
'backup.s3_storage_id' => 'Select a S3 Storage',
];
#[Rule(['required', 'boolean'])]
public bool $saveS3 = false;
#[Rule(['required', 'integer'])]
public int $s3StorageId = 1;
#[Rule(['nullable', 'string'])]
public ?string $databasesToBackup = null;
#[Rule(['required', 'boolean'])]
public bool $dumpAll = false;
public function mount()
{
$this->parameters = get_route_parameters();
if (is_null(data_get($this->backup, 's3_storage_id'))) {
data_set($this->backup, 's3_storage_id', 'default');
try {
$this->parameters = get_route_parameters();
$this->syncData();
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->customValidate();
$this->backup->enabled = $this->backupEnabled;
$this->backup->frequency = $this->frequency;
$this->backup->number_of_backups_locally = $this->numberOfBackupsLocally;
$this->backup->save_s3 = $this->saveS3;
$this->backup->s3_storage_id = $this->s3StorageId;
$this->backup->databases_to_backup = $this->databasesToBackup;
$this->backup->dump_all = $this->dumpAll;
$this->backup->save();
} else {
$this->backupEnabled = $this->backup->enabled;
$this->frequency = $this->backup->frequency;
$this->numberOfBackupsLocally = $this->backup->number_of_backups_locally;
$this->saveS3 = $this->backup->save_s3;
$this->s3StorageId = $this->backup->s3_storage_id;
$this->databasesToBackup = $this->backup->databases_to_backup;
$this->dumpAll = $this->backup->dump_all;
}
}
@ -96,16 +127,14 @@ class BackupEdit extends Component
public function instantSave()
{
try {
$this->custom_validate();
$this->backup->save();
$this->backup->refresh();
$this->syncData(true);
$this->dispatch('success', 'Backup updated successfully.');
} catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage());
}
}
private function custom_validate()
private function customValidate()
{
if (! is_numeric($this->backup->s3_storage_id)) {
$this->backup->s3_storage_id = null;
@ -120,19 +149,14 @@ class BackupEdit extends Component
public function submit()
{
try {
$this->custom_validate();
if ($this->backup->databases_to_backup === '' || $this->backup->databases_to_backup === null) {
$this->backup->databases_to_backup = null;
}
$this->backup->save();
$this->backup->refresh();
$this->dispatch('success', 'Backup updated successfully');
$this->syncData(true);
$this->dispatch('success', 'Backup updated successfully.');
} catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage());
}
}
public function deleteAssociatedBackupsLocally()
private function deleteAssociatedBackupsLocally()
{
$executions = $this->backup->executions;
$backupFolder = null;
@ -152,17 +176,17 @@ class BackupEdit extends Component
$execution->delete();
}
if ($backupFolder) {
if (str($backupFolder)->isNotEmpty()) {
$this->deleteEmptyBackupFolder($backupFolder, $server);
}
}
public function deleteAssociatedBackupsS3()
private function deleteAssociatedBackupsS3()
{
//Add function to delete backups from S3
}
public function deleteAssociatedBackupsSftp()
private function deleteAssociatedBackupsSftp()
{
//Add function to delete backups from SFTP
}

View file

@ -7,6 +7,8 @@ use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneClickhouse;
use Exception;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Rule;
use Livewire\Component;
class General extends Component
@ -15,54 +17,106 @@ class General extends Component
public StandaloneClickhouse $database;
public ?string $db_url = null;
#[Rule(['required', 'string'])]
public string $name;
public ?string $db_url_public = null;
#[Rule(['nullable', 'string'])]
public ?string $description = null;
protected $listeners = ['refresh'];
#[Rule(['required', 'string'])]
public string $clickhouseAdminUser;
protected $rules = [
'database.name' => 'required',
'database.description' => 'nullable',
'database.clickhouse_admin_user' => 'required',
'database.clickhouse_admin_password' => 'required',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
];
#[Rule(['required', 'string'])]
public string $clickhouseAdminPassword;
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.clickhouse_admin_user' => 'Postgres User',
'database.clickhouse_admin_password' => 'Postgres Password',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
];
#[Rule(['required', 'string'])]
public string $image;
#[Rule(['nullable', 'string'])]
public ?string $portsMappings = null;
#[Rule(['nullable', 'boolean'])]
public ?bool $isPublic = null;
#[Rule(['nullable', 'integer'])]
public ?int $publicPort = null;
#[Rule(['nullable', 'string'])]
public ?string $customDockerRunOptions = null;
#[Rule(['nullable', 'string'])]
public ?string $dbUrl = null;
#[Rule(['nullable', 'string'])]
public ?string $dbUrlPublic = null;
#[Rule(['nullable', 'boolean'])]
public bool $isLogDrainEnabled = false;
public function getListeners()
{
$teamId = Auth::user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
];
}
public function mount()
{
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
try {
$this->syncData();
$this->server = data_get($this->database, 'destination.server');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->database->name = $this->name;
$this->database->description = $this->description;
$this->database->clickhouse_admin_user = $this->clickhouseAdminUser;
$this->database->clickhouse_admin_password = $this->clickhouseAdminPassword;
$this->database->image = $this->image;
$this->database->ports_mappings = $this->portsMappings;
$this->database->is_public = $this->isPublic;
$this->database->public_port = $this->publicPort;
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
$this->database->save();
$this->dbUrl = $this->database->internal_db_url;
$this->dbUrlPublic = $this->database->external_db_url;
} else {
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->clickhouseAdminUser = $this->database->clickhouse_admin_user;
$this->clickhouseAdminPassword = $this->database->clickhouse_admin_password;
$this->image = $this->database->image;
$this->portsMappings = $this->database->ports_mappings;
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->dbUrl = $this->database->internal_db_url;
$this->dbUrlPublic = $this->database->external_db_url;
}
}
public function instantSaveAdvanced()
{
try {
if (! $this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->isLogDrainEnabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
@ -73,16 +127,16 @@ class General extends Component
public function instantSave()
{
try {
if ($this->database->is_public && ! $this->database->public_port) {
if ($this->isPublic && ! $this->publicPort) {
$this->dispatch('error', 'Public port is required.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
if ($this->database->is_public) {
if ($this->isPublic) {
if (! str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
@ -92,28 +146,28 @@ class General extends Component
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->db_url_public = $this->database->external_db_url;
$this->database->save();
$this->dbUrlPublic = $this->database->external_db_url;
$this->syncData(true);
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
$this->isPublic = ! $this->isPublic;
$this->syncData(true);
return handleError($e, $this);
}
}
public function refresh(): void
public function databaseProxyStopped()
{
$this->database->refresh();
$this->syncData();
}
public function submit()
{
try {
if (str($this->database->public_port)->isEmpty()) {
$this->database->public_port = null;
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
$this->validate();
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);

View file

@ -4,44 +4,45 @@ namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Illuminate\Support\Collection;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Rule;
use Livewire\Component;
class CreateScheduledBackup extends Component
{
public $database;
#[Rule(['required', 'string'])]
public $frequency;
#[Rule(['required', 'boolean'])]
public bool $saveToS3 = false;
#[Locked]
public $database;
public bool $enabled = true;
public bool $save_s3 = false;
#[Rule(['required', 'integer'])]
public int $s3StorageId;
public $s3_storage_id;
public Collection $s3s;
protected $rules = [
'frequency' => 'required|string',
'save_s3' => 'required|boolean',
];
protected $validationAttributes = [
'frequency' => 'Backup Frequency',
'save_s3' => 'Save to S3',
];
public Collection $definedS3s;
public function mount()
{
$this->s3s = currentTeam()->s3s;
if ($this->s3s->count() > 0) {
$this->s3_storage_id = $this->s3s->first()->id;
try {
$this->definedS3s = currentTeam()->s3s;
if ($this->definedS3s->count() > 0) {
$this->s3StorageId = $this->definedS3s->first()->id;
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit(): void
public function submit()
{
try {
$this->validate();
$isValid = validate_cron_expression($this->frequency);
if (! $isValid) {
$this->dispatch('error', 'Invalid Cron / Human expression.');
@ -51,8 +52,8 @@ class CreateScheduledBackup extends Component
$payload = [
'enabled' => true,
'frequency' => $this->frequency,
'save_s3' => $this->save_s3,
's3_storage_id' => $this->s3_storage_id,
'save_s3' => $this->saveToS3,
's3_storage_id' => $this->s3StorageId,
'database_id' => $this->database->id,
'database_type' => $this->database->getMorphClass(),
'team_id' => currentTeam()->id,
@ -72,10 +73,10 @@ class CreateScheduledBackup extends Component
$this->dispatch('refreshScheduledBackups');
}
} catch (\Throwable $e) {
handleError($e, $this);
return handleError($e, $this);
} finally {
$this->frequency = '';
$this->save_s3 = true;
$this->saveToS3 = true;
}
}
}

View file

@ -7,60 +7,111 @@ use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneDragonfly;
use Exception;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Rule;
use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneDragonfly $database;
public ?string $db_url = null;
#[Rule(['required', 'string'])]
public string $name;
public ?string $db_url_public = null;
#[Rule(['nullable', 'string'])]
public ?string $description = null;
protected $rules = [
'database.name' => 'required',
'database.description' => 'nullable',
'database.dragonfly_password' => 'required',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
];
#[Rule(['required', 'string'])]
public string $dragonflyPassword;
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.dragonfly_password' => 'Redis Password',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
];
#[Rule(['required', 'string'])]
public string $image;
#[Rule(['nullable', 'string'])]
public ?string $portsMappings = null;
#[Rule(['nullable', 'boolean'])]
public ?bool $isPublic = null;
#[Rule(['nullable', 'integer'])]
public ?int $publicPort = null;
#[Rule(['nullable', 'string'])]
public ?string $customDockerRunOptions = null;
#[Rule(['nullable', 'string'])]
public ?string $dbUrl = null;
#[Rule(['nullable', 'string'])]
public ?string $dbUrlPublic = null;
#[Rule(['nullable', 'boolean'])]
public bool $isLogDrainEnabled = false;
public function getListeners()
{
$teamId = Auth::user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
];
}
public function mount()
{
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
try {
$this->syncData();
$this->server = data_get($this->database, 'destination.server');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->database->name = $this->name;
$this->database->description = $this->description;
$this->database->dragonfly_password = $this->dragonflyPassword;
$this->database->image = $this->image;
$this->database->ports_mappings = $this->portsMappings;
$this->database->is_public = $this->isPublic;
$this->database->public_port = $this->publicPort;
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
$this->database->save();
$this->dbUrl = $this->database->internal_db_url;
$this->dbUrlPublic = $this->database->external_db_url;
} else {
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->dragonflyPassword = $this->database->dragonfly_password;
$this->image = $this->database->image;
$this->portsMappings = $this->database->ports_mappings;
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->dbUrl = $this->database->internal_db_url;
$this->dbUrlPublic = $this->database->external_db_url;
}
}
public function instantSaveAdvanced()
{
try {
if (! $this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->isLogDrainEnabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
@ -68,11 +119,50 @@ class General extends Component
}
}
public function instantSave()
{
try {
if ($this->isPublic && ! $this->publicPort) {
$this->dispatch('error', 'Public port is required.');
$this->isPublic = false;
return;
}
if ($this->isPublic) {
if (! str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->isPublic = false;
return;
}
StartDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->dbUrlPublic = $this->database->external_db_url;
$this->syncData(true);
} catch (\Throwable $e) {
$this->isPublic = ! $this->isPublic;
$this->syncData(true);
return handleError($e, $this);
}
}
public function databaseProxyStopped()
{
$this->syncData();
}
public function submit()
{
try {
$this->validate();
$this->database->save();
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
@ -84,45 +174,4 @@ class General extends Component
}
}
}
public function instantSave()
{
try {
if ($this->database->is_public && ! $this->database->public_port) {
$this->dispatch('error', 'Public port is required.');
$this->database->is_public = false;
return;
}
if ($this->database->is_public) {
if (! str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
return;
}
StartDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->db_url_public = $this->database->external_db_url;
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
return handleError($e, $this);
}
}
public function refresh(): void
{
$this->database->refresh();
}
public function render()
{
return view('livewire.project.database.dragonfly.general');
}
}

View file

@ -6,6 +6,7 @@ use App\Actions\Database\RestartDatabase;
use App\Actions\Database\StartDatabase;
use App\Actions\Database\StopDatabase;
use App\Actions\Docker\GetContainersStatus;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Heading extends Component
@ -18,7 +19,7 @@ class Heading extends Component
public function getListeners()
{
$userId = auth()->user()->id;
$userId = Auth::id();
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => 'activityFinished',

View file

@ -3,6 +3,7 @@
namespace App\Livewire\Project\Database;
use App\Models\Server;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Livewire\Component;
@ -46,7 +47,7 @@ class Import extends Component
public function getListeners()
{
$userId = auth()->user()->id;
$userId = Auth::id();
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',

View file

@ -3,39 +3,39 @@
namespace App\Livewire\Project\Database;
use Exception;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Rule;
use Livewire\Component;
class InitScript extends Component
{
#[Locked]
public array $script;
#[Locked]
public int $index;
public ?string $filename;
#[Rule(['nullable', 'string'])]
public ?string $filename = null;
public ?string $content;
protected $rules = [
'filename' => 'required|string',
'content' => 'required|string',
];
protected $validationAttributes = [
'filename' => 'Filename',
'content' => 'Content',
];
#[Rule(['nullable', 'string'])]
public ?string $content = null;
public function mount()
{
$this->index = data_get($this->script, 'index');
$this->filename = data_get($this->script, 'filename');
$this->content = data_get($this->script, 'content');
try {
$this->index = data_get($this->script, 'index');
$this->filename = data_get($this->script, 'filename');
$this->content = data_get($this->script, 'content');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function submit()
{
$this->validate();
try {
$this->validate();
$this->script['index'] = $this->index;
$this->script['content'] = $this->content;
$this->script['filename'] = $this->filename;
@ -47,6 +47,10 @@ class InitScript extends Component
public function delete()
{
$this->dispatch('delete_init_script', $this->script);
try {
$this->dispatch('delete_init_script', $this->script);
} catch (Exception $e) {
return handleError($e, $this);
}
}
}

View file

@ -7,62 +7,116 @@ use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneKeydb;
use Exception;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Rule;
use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneKeydb $database;
public ?string $db_url = null;
#[Rule(['required', 'string'])]
public string $name;
public ?string $db_url_public = null;
#[Rule(['nullable', 'string'])]
public ?string $description = null;
protected $rules = [
'database.name' => 'required',
'database.description' => 'nullable',
'database.keydb_conf' => 'nullable',
'database.keydb_password' => 'required',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
];
#[Rule(['nullable', 'string'])]
public ?string $keydbConf = null;
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.keydb_conf' => 'Redis Configuration',
'database.keydb_password' => 'Redis Password',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
];
#[Rule(['required', 'string'])]
public string $keydbPassword;
#[Rule(['required', 'string'])]
public string $image;
#[Rule(['nullable', 'string'])]
public ?string $portsMappings = null;
#[Rule(['nullable', 'boolean'])]
public ?bool $isPublic = null;
#[Rule(['nullable', 'integer'])]
public ?int $publicPort = null;
#[Rule(['nullable', 'string'])]
public ?string $customDockerRunOptions = null;
#[Rule(['nullable', 'string'])]
public ?string $dbUrl = null;
#[Rule(['nullable', 'string'])]
public ?string $dbUrlPublic = null;
#[Rule(['nullable', 'boolean'])]
public bool $isLogDrainEnabled = false;
public function getListeners()
{
$teamId = Auth::user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
];
}
public function mount()
{
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
try {
$this->syncData();
$this->server = data_get($this->database, 'destination.server');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->database->name = $this->name;
$this->database->description = $this->description;
$this->database->keydb_conf = $this->keydbConf;
$this->database->keydb_password = $this->keydbPassword;
$this->database->image = $this->image;
$this->database->ports_mappings = $this->portsMappings;
$this->database->is_public = $this->isPublic;
$this->database->public_port = $this->publicPort;
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
$this->database->save();
$this->dbUrl = $this->database->internal_db_url;
$this->dbUrlPublic = $this->database->external_db_url;
} else {
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->keydbConf = $this->database->keydb_conf;
$this->keydbPassword = $this->database->keydb_password;
$this->image = $this->database->image;
$this->portsMappings = $this->database->ports_mappings;
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->dbUrl = $this->database->internal_db_url;
$this->dbUrlPublic = $this->database->external_db_url;
}
}
public function instantSaveAdvanced()
{
try {
if (! $this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->isLogDrainEnabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
@ -70,14 +124,50 @@ class General extends Component
}
}
public function instantSave()
{
try {
if ($this->isPublic && ! $this->publicPort) {
$this->dispatch('error', 'Public port is required.');
$this->isPublic = false;
return;
}
if ($this->isPublic) {
if (! str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->isPublic = false;
return;
}
StartDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->dbUrlPublic = $this->database->external_db_url;
$this->syncData(true);
} catch (\Throwable $e) {
$this->isPublic = ! $this->isPublic;
$this->syncData(true);
return handleError($e, $this);
}
}
public function databaseProxyStopped()
{
$this->syncData();
}
public function submit()
{
try {
$this->validate();
if ($this->database->keydb_conf === '') {
$this->database->keydb_conf = null;
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
@ -89,45 +179,4 @@ class General extends Component
}
}
}
public function instantSave()
{
try {
if ($this->database->is_public && ! $this->database->public_port) {
$this->dispatch('error', 'Public port is required.');
$this->database->is_public = false;
return;
}
if ($this->database->is_public) {
if (! str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
return;
}
StartDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->db_url_public = $this->database->external_db_url;
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
return handleError($e, $this);
}
}
public function refresh(): void
{
$this->database->refresh();
}
public function render()
{
return view('livewire.project.database.keydb.general');
}
}

View file

@ -326,7 +326,7 @@ class Select extends Component
public function loadServers()
{
$this->servers = Server::isUsable()->get();
$this->servers = Server::isUsable()->get()->sortBy('name');
$this->allServers = $this->servers;
}
}

View file

@ -4,6 +4,7 @@ namespace App\Livewire\Project\Service;
use App\Actions\Docker\GetContainersStatus;
use App\Models\Service;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Configuration extends Component
@ -20,7 +21,7 @@ class Configuration extends Component
public function getListeners()
{
$userId = auth()->user()->id;
$userId = Auth::id();
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',

View file

@ -7,6 +7,7 @@ use App\Actions\Service\StopService;
use App\Actions\Shared\PullImage;
use App\Events\ServiceStatusChanged;
use App\Models\Service;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use Spatie\Activitylog\Models\Activity;
@ -34,7 +35,7 @@ class Navbar extends Component
public function getListeners()
{
$userId = auth()->user()->id;
$userId = Auth::id();
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',

View file

@ -52,7 +52,7 @@ class LogDrains extends Component
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->customValidation();
$this->server->settings->is_logdrain_newrelic_enabled = $this->isLogDrainNewRelicEnabled;
$this->server->settings->is_logdrain_axiom_enabled = $this->isLogDrainAxiomEnabled;
$this->server->settings->is_logdrain_custom_enabled = $this->isLogDrainCustomEnabled;
@ -79,6 +79,44 @@ class LogDrains extends Component
}
}
public function customValidation()
{
if ($this->isLogDrainNewRelicEnabled) {
try {
$this->validate([
'logDrainNewRelicLicenseKey' => ['required'],
'logDrainNewRelicBaseUri' => ['required', 'url'],
]);
} catch (\Throwable $e) {
$this->isLogDrainNewRelicEnabled = false;
throw $e;
}
} elseif ($this->isLogDrainAxiomEnabled) {
try {
$this->validate([
'logDrainAxiomDatasetName' => ['required'],
'logDrainAxiomApiKey' => ['required'],
]);
} catch (\Throwable $e) {
$this->isLogDrainAxiomEnabled = false;
throw $e;
}
} elseif ($this->isLogDrainCustomEnabled) {
try {
$this->validate([
'logDrainCustomConfig' => ['required'],
'logDrainCustomConfigParser' => ['string', 'nullable'],
]);
} catch (\Throwable $e) {
$this->isLogDrainCustomEnabled = false;
throw $e;
}
}
}
public function instantSave()
{
try {

View file

@ -2,6 +2,7 @@
namespace App\Livewire\Subscription;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use Stripe\Checkout\Session;
use Stripe\Stripe;
@ -26,7 +27,7 @@ class PricingPlans extends Component
$payload = [
'allow_promotion_codes' => true,
'billing_address_collection' => 'required',
'client_reference_id' => auth()->user()->id.':'.currentTeam()->id,
'client_reference_id' => Auth::id().':'.currentTeam()->id,
'line_items' => [[
'price' => $priceId,
'adjustable_quantity' => [
@ -43,7 +44,7 @@ class PricingPlans extends Component
],
'subscription_data' => [
'metadata' => [
'user_id' => auth()->user()->id,
'user_id' => Auth::id(),
'team_id' => currentTeam()->id,
],
],
@ -60,7 +61,7 @@ class PricingPlans extends Component
'name' => 'auto',
];
} else {
$payload['customer_email'] = auth()->user()->email;
$payload['customer_email'] = Auth::user()->email;
}
$session = Session::create($payload);

View file

@ -4,6 +4,7 @@ namespace App\Livewire\Team;
use App\Models\Team;
use App\Models\TeamInvitation;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
@ -55,7 +56,7 @@ class Index extends Component
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === auth()->user()->id) {
if ($user->id === Auth::id()) {
return;
}
$user->teams()->detach($currentTeam);

View file

@ -10,6 +10,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL;
@ -158,7 +159,7 @@ class User extends Authenticatable implements SendsEmail
public function isAdminFromSession()
{
if (auth()->user()->id === 0) {
if (Auth::id() === 0) {
return true;
}
$teams = $this->teams()->get();
@ -178,9 +179,9 @@ class User extends Authenticatable implements SendsEmail
public function isInstanceAdmin()
{
$found_root_team = auth()->user()->teams->filter(function ($team) {
$found_root_team = Auth::user()->teams->filter(function ($team) {
if ($team->id == 0) {
if (! auth()->user()->isAdmin()) {
if (! Auth::user()->isAdmin()) {
return false;
}
@ -195,9 +196,9 @@ class User extends Authenticatable implements SendsEmail
public function currentTeam()
{
return Cache::remember('team:'.auth()->user()->id, 3600, function () {
if (is_null(data_get(session('currentTeam'), 'id')) && auth()->user()->teams->count() > 0) {
return auth()->user()->teams[0];
return Cache::remember('team:'.Auth::id(), 3600, function () {
if (is_null(data_get(session('currentTeam'), 'id')) && Auth::user()->teams->count() > 0) {
return Auth::user()->teams[0];
}
return Team::find(session('currentTeam')->id);
@ -206,7 +207,7 @@ class User extends Authenticatable implements SendsEmail
public function otherTeams()
{
return auth()->user()->teams->filter(function ($team) {
return Auth::user()->teams->filter(function ($team) {
return $team->id != currentTeam()->id;
});
}
@ -216,7 +217,7 @@ class User extends Authenticatable implements SendsEmail
if (data_get($this, 'pivot')) {
return $this->pivot->role;
}
$user = auth()->user()->teams->where('id', currentTeam()->id)->first();
$user = Auth::user()->teams->where('id', currentTeam()->id)->first();
return data_get($user, 'pivot.role');
}

View file

@ -35,6 +35,7 @@ use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Process\Pool;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
@ -100,12 +101,12 @@ function isInstanceAdmin()
function currentTeam()
{
return auth()?->user()?->currentTeam() ?? null;
return Auth::user()?->currentTeam() ?? null;
}
function showBoarding(): bool
{
if (auth()->user()?->isMember()) {
if (Auth::user()->isMember()) {
return false;
}
@ -114,14 +115,14 @@ function showBoarding(): bool
function refreshSession(?Team $team = null): void
{
if (! $team) {
if (auth()->user()?->currentTeam()) {
$team = Team::find(auth()->user()->currentTeam()->id);
if (Auth::user()->currentTeam()) {
$team = Team::find(Auth::user()->currentTeam()->id);
} else {
$team = User::find(auth()->user()->id)->teams->first();
$team = User::find(Auth::id())->teams->first();
}
}
Cache::forget('team:'.auth()->user()->id);
Cache::remember('team:'.auth()->user()->id, 3600, function () use ($team) {
Cache::forget('team:'.Auth::id());
Cache::remember('team:'.Auth::id(), 3600, function () use ($team) {
return $team;
});
session(['currentTeam' => $team]);

View file

@ -10,7 +10,7 @@
])
<div @class([
'flex flex-row items-center gap-4 px-2 py-1 form-control min-w-fit dark:hover:bg-coolgray-100',
'flex flex-row items-center gap-4 pr-2 py-1 form-control min-w-fit dark:hover:bg-coolgray-100',
'w-full' => $fullWidth,
])>
@if (!$hideLabel)

View file

@ -2,14 +2,14 @@
<div class="flex items-center gap-2">
<h2>Preview Deployments</h2>
<x-forms.button type="submit">Save</x-forms.button>
<x-forms.button wire:click="resetToDefault">Reset template to default</x-forms.button>
<x-forms.button isHighlighted wire:click="resetToDefault">Reset template to default</x-forms.button>
</div>
<div class="pb-4 ">Preview Deployments based on pull requests are here.</div>
<div class="flex flex-col gap-2 pb-4">
<x-forms.input id="application.preview_url_template" label="Preview URL Template"
<x-forms.input id="previewUrlTemplate" label="Preview URL Template"
helper="Templates:<span class='text-helper'>@@{{ random }}</span> to generate random sub-domain each time a PR is deployed, <span class='text-helper'>@@{{ pr_id }}</span> to use pull request ID as sub-domain or <span class='text-helper'>@@{{ domain }}</span> to replace the domain name with the application's domain name." />
@if ($preview_url_template)
<div class="">Domain Preview: {{ $preview_url_template }}</div>
@if ($previewUrlTemplate)
<div class="">Domain Preview: {{ $previewUrlTemplate }}</div>
@endif
</div>
</form>

View file

@ -6,15 +6,13 @@
Save
</x-forms.button>
</div>
{{-- <div>Advanced Swarm Configuration</div> --}}
<div class="flex flex-col gap-2 py-4">
<div class="flex flex-col items-end gap-2 xl:flex-row">
<x-forms.input id="application.swarm_replicas" label="Replicas" required />
<x-forms.input id="swarmReplicas" label="Replicas" required />
<x-forms.checkbox instantSave helper="If turned off, this resource will start on manager nodes too."
id="application.settings.is_swarm_only_worker_nodes" label="Only Start on Worker nodes" />
id="isSwarmOnlyWorkerNodes" label="Only Start on Worker nodes" />
</div>
<x-forms.textarea id="swarm_placement_constraints" rows="7"
label="Custom Placement Constraints"
<x-forms.textarea id="swarmPlacementConstraints" rows="7" label="Custom Placement Constraints"
placeholder="placement:
constraints:
- 'node.role == worker'" />

View file

@ -19,12 +19,12 @@
@endif
</div>
<div class="w-48 pb-2">
<x-forms.checkbox instantSave label="Backup Enabled" id="backup.enabled" />
<x-forms.checkbox instantSave label="S3 Enabled" id="backup.save_s3" />
<x-forms.checkbox instantSave label="Backup Enabled" id="backupEnabled" />
<x-forms.checkbox instantSave label="S3 Enabled" id="saveS3" />
</div>
@if ($backup->save_s3)
<div class="pb-6">
<x-forms.select id="backup.s3_storage_id" label="S3 Storage" required>
<x-forms.select id="s3StorageId" label="S3 Storage" required>
<option value="default">Select a S3 storage</option>
@foreach ($s3s as $s3)
<option value="{{ $s3->id }}">{{ $s3->name }}</option>
@ -37,40 +37,40 @@
<div class="flex gap-2 flex-col ">
@if ($backup->database_type === 'App\Models\StandalonePostgresql' && $backup->database_id !== 0)
<div class="w-48">
<x-forms.checkbox label="Backup All Databases" id="backup.dump_all" instantSave />
<x-forms.checkbox label="Backup All Databases" id="dumpAll" instantSave />
</div>
@if (!$backup->dump_all)
<x-forms.input label="Databases To Backup"
helper="Comma separated list of databases to backup. Empty will include the default one."
id="backup.databases_to_backup" />
id="databasesToBackup" />
@endif
@elseif($backup->database_type === 'App\Models\StandaloneMongodb')
<x-forms.input label="Databases To Include"
helper="A list of databases to backup. You can specify which collection(s) per database to exclude from the backup. Empty will include all databases and collections.<br><br>Example:<br><br>database1:collection1,collection2|database2:collection3,collection4<br><br> database1 will include all collections except collection1 and collection2. <br>database2 will include all collections except collection3 and collection4.<br><br>Another Example:<br><br>database1:collection1|database2<br><br> database1 will include all collections except collection1.<br>database2 will include ALL collections."
id="backup.databases_to_backup" />
id="databasesToBackup" />
@elseif($backup->database_type === 'App\Models\StandaloneMysql')
<div class="w-48">
<x-forms.checkbox label="Backup All Databases" id="backup.dump_all" instantSave />
<x-forms.checkbox label="Backup All Databases" id="dumpAll" instantSave />
</div>
@if (!$backup->dump_all)
<x-forms.input label="Databases To Backup"
helper="Comma separated list of databases to backup. Empty will include the default one."
id="backup.databases_to_backup" />
id="databasesToBackup" />
@endif
@elseif($backup->database_type === 'App\Models\StandaloneMariadb')
<div class="w-48">
<x-forms.checkbox label="Backup All Databases" id="backup.dump_all" instantSave />
<x-forms.checkbox label="Backup All Databases" id="dumpAll" instantSave />
</div>
@if (!$backup->dump_all)
<x-forms.input label="Databases To Backup"
helper="Comma separated list of databases to backup. Empty will include the default one."
id="backup.databases_to_backup" />
id="databasesToBackup" />
@endif
@endif
</div>
<div class="flex gap-2">
<x-forms.input label="Frequency" id="backup.frequency" />
<x-forms.input label="Number of backups to keep (locally)" id="backup.number_of_backups_locally" />
<x-forms.input label="Frequency" id="frequency" />
<x-forms.input label="Number of backups to keep (locally)" id="numberOfBackupsLocally" />
</div>
</div>
</form>

View file

@ -7,68 +7,75 @@
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required
<x-forms.input label="Name" id="name" />
<x-forms.input label="Description" id="description" />
<x-forms.input label="Image" id="image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/r/clickhouse/clickhouse-server/'>https://hub.docker.com/r/clickhouse/clickhouse-server/</a>" />
</div>
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Initial Username" id="database.clickhouse_admin_user"
placeholder="If empty: clickhouse" readonly helper="You can only change this in the database." />
<x-forms.input label="Initial Password" id="database.clickhouse_admin_password" type="password" required
<x-forms.input label="Initial Username" id="clickhouseAdminUser" placeholder="If empty: clickhouse"
readonly helper="You can only change this in the database." />
<x-forms.input label="Initial Password" id="clickhouseAdminPassword" type="password" required readonly
helper="You can only change this in the database." />
</div>
@else
<div class=" dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2">
<x-forms.input label="Username" id="database.clickhouse_admin_user" required />
<x-forms.input label="Password" id="database.clickhouse_admin_password" type="password" required />
<x-forms.input label="Username" id="clickhouseAdminUser" required />
<x-forms.input label="Password" id="clickhouseAdminPassword" type="password" required />
</div>
@endif
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
id="customDockerRunOptions" label="Custom Docker Options" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
<x-forms.input placeholder="3000:5432" id="portsMappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" />
</div>
<x-forms.input label="Clickhouse URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url" />
@if ($db_url_public)
type="password" readonly wire:model="dbUrl" />
@if ($dbUrlPublic)
<x-forms.input label="Clickhouse URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url_public" />
type="password" readonly wire:model="dbUrlPublic" />
@else
<x-forms.input label="Clickhouse URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
readonly value="Starting the database will generate this." />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
<div class="flex flex-col py-2 w-64">
<div class="flex items-center gap-2 pb-2">
<h3>Proxy</h3>
@if ($isPublic)
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !$isPublic }}"
@click="slideOverOpen=true">Logs</x-forms.button>
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" />
</div>
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port" />
</div>
</form>
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<div class="w-64">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" />
</div>
</div>

View file

@ -2,13 +2,13 @@
<x-forms.input placeholder="0 0 * * * or daily" id="frequency"
helper="You can use every_minute, hourly, daily, weekly, monthly, yearly or a cron expression." label="Frequency"
required />
@if ($s3s->count() === 0)
@if ($definedS3s->count() === 0)
<div class="text-red-500">No validated S3 Storages found.</div>
@else
<x-forms.checkbox wire:model.live="save_s3" label="Save to S3" />
@if ($save_s3)
<x-forms.select id="selected_storage_id" label="Select a validated S3 storage">
@foreach ($s3s as $s3)
<x-forms.checkbox wire:model.live="saveToS3" label="Save to S3" />
@if ($saveToS3)
<x-forms.select id="s3StorageId" label="Select a S3 Storage">
@foreach ($definedS3s as $s3)
<option value="{{ $s3->id }}">{{ $s3->name }}</option>
@endforeach
</x-forms.select>

View file

@ -7,53 +7,72 @@
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required />
<x-forms.input label="Name" id="name" />
<x-forms.input label="Description" id="description" />
<x-forms.input label="Image" id="image" required />
</div>
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
id="customDockerRunOptions" label="Custom Docker Options" />
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Initial Password" id="dragonflyPassword" type="password" required readonly
helper="You can only change this in the database." />
</div>
@else
<div class=" dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2">
<x-forms.input label="Password" id="dragonflyPassword" type="password" required />
</div>
@endif
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
<x-forms.input placeholder="3000:5432" id="portsMappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" />
</div>
<x-forms.input label="Dragonfly URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url" />
@if ($db_url_public)
type="password" readonly wire:model="dbUrl" />
@if ($dbUrlPublic)
<x-forms.input label="Dragonfly URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url_public" />
type="password" readonly wire:model="dbUrlPublic" />
@else
<x-forms.input label="Dragonfly URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
readonly value="Starting the database will generate this." />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
<div class="flex flex-col py-2 w-64">
<div class="flex items-center gap-2 pb-2">
<h3>Proxy</h3>
@if ($isPublic)
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !$isPublic }}"
@click="slideOverOpen=true">Logs</x-forms.button>
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" />
</div>
</div>
{{-- <x-forms.textarea
helper="<a target='_blank' class='underline dark:text-white' href='https://raw.githubusercontent.com/Snapchat/KeyDB/unstable/keydb.conf'>KeyDB Default Configuration</a>"
label="Custom Dragonfly Configuration" rows="10" id="database.keydb_conf" /> --}}
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port" />
</div>
</form>
<h3 class="pt-4">Advanced</h3>
<div class="w-64">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" />
</div>
</div>

View file

@ -7,54 +7,75 @@
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required
<x-forms.input label="Name" id="name" />
<x-forms.input label="Description" id="description" />
<x-forms.input label="Image" id="image" required
helper="For all available images, check here:<br><br><a target='_blank' href=https://hub.docker.com/r/eqalpha/keydb'>https://hub.docker.com/r/eqalpha/keydb</a>" />
</div>
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Initial Password" id="keydbPassword" type="password" required readonly
helper="You can only change this in the database." />
</div>
@else
<div class=" dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2">
<x-forms.input label="Password" id="keydbPassword" type="password" required />
</div>
@endif
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
id="customDockerRunOptions" label="Custom Docker Options" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
<x-forms.input placeholder="3000:5432" id="portsMappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" />
</div>
<x-forms.input label="KeyDB URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url" />
@if ($db_url_public)
type="password" readonly wire:model="dbUrl" />
@if ($dbUrlPublic)
<x-forms.input label="KeyDB URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url_public" />
type="password" readonly wire:model="dbUrlPublic" />
@else
<x-forms.input label="KeyDB URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
readonly value="Starting the database will generate this." />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
<div class="flex flex-col py-2 w-64">
<div class="flex items-center gap-2 pb-2">
<h3>Proxy</h3>
@if ($isPublic)
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !$isPublic }}"
@click="slideOverOpen=true">Logs</x-forms.button>
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" />
</div>
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port" />
</div>
<x-forms.textarea
helper="<a target='_blank' class='underline dark:text-white' href='https://raw.githubusercontent.com/Snapchat/KeyDB/unstable/keydb.conf'>KeyDB Default Configuration</a>"
label="Custom KeyDB Configuration" rows="10" id="database.keydb_conf" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
</div>
label="Custom KeyDB Configuration" rows="10" id="keydbConf" />
</form>
<h3 class="pt-4">Advanced</h3>
<div class="w-64">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" />
</div>
</div>

View file

@ -38,7 +38,7 @@
<div class="flex gap-2 ">
<h2 class="pb-4">Scheduled Backups</h2>
<x-modal-input buttonTitle="+ Add" title="New Scheduled Backup">
<livewire:project.database.create-scheduled-backup :database="$serviceDatabase" :s3s="$s3s" />
<livewire:project.database.create-scheduled-backup :database="$serviceDatabase" />
</x-modal-input>
</div>
<livewire:project.database.scheduled-backups :database="$serviceDatabase" />

View file

@ -11,7 +11,7 @@
<h2>Log Drains</h2>
<x-loading wire:target="instantSave" wire:loading.delay />
</div>
<div class="">Sends service logs to 3rd party tools.</div>
<div>Sends service logs to 3rd party tools.</div>
<div class="flex flex-col gap-4 pt-4">
<div class="p-4 border dark:border-coolgray-300">
<form wire:submit='submit("newrelic")' class="flex flex-col">

View file

@ -1,6 +1,6 @@
<tr @class([
'dark:text-white text-black dark:bg-coolblack dark:hover:bg-coolgray-100',
'dark:bg-coolgray-100 bg-neutral-200' => $member->id == auth()->user()->id,
'dark:bg-coolgray-100 bg-neutral-200' => $member->id == Auth::id(),
])>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ $member->name }}
@ -12,9 +12,9 @@
{{ data_get($member, 'pivot.role') }}
</td>
<td class="flex gap-2 px-5 py-4 text-sm whitespace-nowrap">
@if (auth()->user()->isAdminFromSession())
@if ($member->id !== auth()->user()->id)
@if (auth()->user()->isOwner())
@if (Auth::user()->isAdminFromSession())
@if ($member->id !== Auth::id())
@if (Auth::user()->isOwner())
@if (data_get($member, 'pivot.role') === 'owner')
<x-forms.button wire:click="makeAdmin">To Admin</x-forms.button>
<x-forms.button wire:click="makeReadonly">To Member</x-forms.button>
@ -30,7 +30,7 @@
<x-forms.button wire:click="makeAdmin">To Admin</x-forms.button>
<x-forms.button isError wire:click="remove">Remove</x-forms.button>
@endif
@elseif (auth()->user()->isAdmin())
@elseif (Auth::user()->isAdmin())
@if (data_get($member, 'pivot.role') === 'admin')
<x-forms.button wire:click="makeReadonly">To Member</x-forms.button>
<x-forms.button isError wire:click="remove">Remove</x-forms.button>

View file

@ -12,6 +12,7 @@
*/
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('team.{teamId}', function (User $user, int $teamId) {
@ -23,7 +24,7 @@ Broadcast::channel('team.{teamId}', function (User $user, int $teamId) {
});
Broadcast::channel('user.{userId}', function (User $user) {
if ($user->id === auth()->user()->id) {
if ($user->id === Auth::id()) {
return true;
}