Merge branch 'next' into feat/disable-default-redirect

This commit is contained in:
Kael
2024-10-31 23:57:51 +11:00
committed by GitHub
180 changed files with 2400 additions and 1911 deletions

View File

@@ -73,8 +73,6 @@ class Index extends Component
}
$this->privateKeyName = generate_random_name();
$this->remoteServerName = generate_random_name();
$this->remoteServerPort = $this->remoteServerPort;
$this->remoteServerUser = $this->remoteServerUser;
if (isDev()) {
$this->privateKey = '-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
@@ -87,26 +85,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->remoteServerDescription = 'Created by Coolify';
$this->remoteServerHost = 'coolify-testing-host';
}
// if ($this->currentState === 'create-project') {
// $this->getProjects();
// }
// if ($this->currentState === 'create-resource') {
// $this->selectExistingServer();
// $this->selectExistingProject();
// }
// if ($this->currentState === 'private-key') {
// $this->setServerType('remote');
// }
// if ($this->currentState === 'create-server') {
// $this->selectExistingPrivateKey();
// }
// if ($this->currentState === 'validate-server') {
// $this->selectExistingServer();
// }
// if ($this->currentState === 'select-existing-server') {
// $this->selectExistingServer();
// }
}
public function explanation()

View File

@@ -29,7 +29,7 @@ class Form extends Component
public function delete()
{
try {
if ($this->destination->getMorphClass() === 'App\Models\StandaloneDocker') {
if ($this->destination->getMorphClass() === \App\Models\StandaloneDocker::class) {
if ($this->destination->attachedTo()) {
return $this->dispatch('error', 'You must delete all resources before deleting this destination.');
}

View File

@@ -4,6 +4,7 @@ namespace App\Livewire;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
use Livewire\Component;
class ForcePasswordReset extends Component
@@ -16,14 +17,19 @@ class ForcePasswordReset extends Component
public string $password_confirmation;
protected $rules = [
'email' => 'required|email',
'password' => 'required|min:8',
'password_confirmation' => 'required|same:password',
];
public function rules(): array
{
return [
'email' => ['required', 'email'],
'password' => ['required', Password::defaults(), 'confirmed'],
];
}
public function mount()
{
if (auth()->user()->force_password_reset === false) {
return redirect()->route('dashboard');
}
$this->email = auth()->user()->email;
}
@@ -34,6 +40,10 @@ class ForcePasswordReset extends Component
public function submit()
{
if (auth()->user()->force_password_reset === false) {
return redirect()->route('dashboard');
}
try {
$this->rateLimit(10);
$this->validate();

View File

@@ -68,7 +68,6 @@ class NewActivityMonitor extends Component
} else {
$this->dispatch($this->eventToDispatch);
}
ray('Dispatched event: '.$this->eventToDispatch.' with data: '.$this->eventData);
}
}
}

View File

@@ -35,7 +35,6 @@ class Discord extends Component
try {
$this->submit();
} catch (\Throwable $e) {
ray($e->getMessage());
$this->team->discord_enabled = false;
$this->validate();
}

View File

@@ -42,7 +42,6 @@ class Telegram extends Component
try {
$this->submit();
} catch (\Throwable $e) {
ray($e->getMessage());
$this->team->telegram_enabled = false;
$this->validate();
}

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Profile;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
use Livewire\Attributes\Validate;
use Livewire\Component;
@@ -48,9 +49,8 @@ class Index extends Component
{
try {
$this->validate([
'current_password' => 'required',
'new_password' => 'required|min:8',
'new_password_confirmation' => 'required|min:8|same:new_password',
'current_password' => ['required'],
'new_password' => ['required', Password::defaults(), 'confirmed'],
]);
if (! Hash::check($this->current_password, auth()->user()->password)) {
$this->dispatch('error', 'Current password is incorrect.');

View File

@@ -46,8 +46,6 @@ class DeploymentNavbar extends Component
try {
force_start_deployment($this->application_deployment_queue);
} catch (\Throwable $e) {
ray($e);
return handleError($e, $this);
}
}
@@ -81,8 +79,6 @@ class DeploymentNavbar extends Component
}
instant_remote_process([$kill_command], $server);
} catch (\Throwable $e) {
ray($e);
return handleError($e, $this);
} finally {
$this->application_deployment_queue->update([

View File

@@ -24,10 +24,10 @@ class Index extends Component
}
// No backups
if (
$database->getMorphClass() === 'App\Models\StandaloneRedis' ||
$database->getMorphClass() === 'App\Models\StandaloneKeydb' ||
$database->getMorphClass() === 'App\Models\StandaloneDragonfly' ||
$database->getMorphClass() === 'App\Models\StandaloneClickhouse'
$database->getMorphClass() === \App\Models\StandaloneRedis::class ||
$database->getMorphClass() === \App\Models\StandaloneKeydb::class ||
$database->getMorphClass() === \App\Models\StandaloneDragonfly::class ||
$database->getMorphClass() === \App\Models\StandaloneClickhouse::class
) {
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,

View File

@@ -77,7 +77,7 @@ class BackupEdit extends Component
$this->backup->delete();
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
if ($this->backup->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
$previousUrl = url()->previous();
$url = Url::fromString($previousUrl);
$url = $url->withoutQueryParameter('selectedBackupId');
@@ -138,7 +138,7 @@ class BackupEdit extends Component
$backupFolder = null;
foreach ($executions as $execution) {
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
if ($this->backup->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
$server = $this->backup->database->service->destination->server;
} else {
$server = $this->backup->database->destination->server;

View File

@@ -57,7 +57,7 @@ class BackupExecutions extends Component
return;
}
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->service->destination->server);
} else {
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);

View File

@@ -66,7 +66,7 @@ class CreateScheduledBackup extends Component
}
$databaseBackup = ScheduledDatabaseBackup::create($payload);
if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') {
if ($this->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
$this->dispatch('refreshScheduledBackups', $databaseBackup->id);
} else {
$this->dispatch('refreshScheduledBackups');

View File

@@ -77,10 +77,10 @@ class Import extends Component
}
if (
$this->resource->getMorphClass() == 'App\Models\StandaloneRedis' ||
$this->resource->getMorphClass() == 'App\Models\StandaloneKeydb' ||
$this->resource->getMorphClass() == 'App\Models\StandaloneDragonfly' ||
$this->resource->getMorphClass() == 'App\Models\StandaloneClickhouse'
$this->resource->getMorphClass() == \App\Models\StandaloneRedis::class ||
$this->resource->getMorphClass() == \App\Models\StandaloneKeydb::class ||
$this->resource->getMorphClass() == \App\Models\StandaloneDragonfly::class ||
$this->resource->getMorphClass() == \App\Models\StandaloneClickhouse::class
) {
$this->unsupported = true;
}
@@ -108,19 +108,19 @@ class Import extends Component
$this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}";
switch ($this->resource->getMorphClass()) {
case 'App\Models\StandaloneMariadb':
case \App\Models\StandaloneMariadb::class:
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mariadbRestoreCommand} < {$tmpPath}'";
$this->importCommands[] = "rm {$tmpPath}";
break;
case 'App\Models\StandaloneMysql':
case \App\Models\StandaloneMysql::class:
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mysqlRestoreCommand} < {$tmpPath}'";
$this->importCommands[] = "rm {$tmpPath}";
break;
case 'App\Models\StandalonePostgresql':
case \App\Models\StandalonePostgresql::class:
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->postgresqlRestoreCommand} {$tmpPath}'";
$this->importCommands[] = "rm {$tmpPath}";
break;
case 'App\Models\StandaloneMongodb':
case \App\Models\StandaloneMongodb::class:
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mongodbRestoreCommand}{$tmpPath}'";
$this->importCommands[] = "rm {$tmpPath}";
break;

View File

@@ -29,7 +29,7 @@ class ScheduledBackups extends Component
$this->setSelectedBackup($this->selectedBackupId, true);
}
$this->parameters = get_route_parameters();
if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') {
if ($this->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
$this->type = 'service-database';
} else {
$this->type = 'database';

View File

@@ -46,7 +46,6 @@ class DockerImage extends Component
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
ray($image, $tag);
$application = Application::create([
'name' => 'docker-image-'.new Cuid2,
'repository_project_id' => 0,

View File

@@ -153,7 +153,6 @@ class GithubPrivateRepository extends Component
protected function loadBranchByPage()
{
ray('Loading page '.$this->page);
$response = Http::withToken($this->token)->get("{$this->github_app->api_url}/repos/{$this->selected_repository_owner}/{$this->selected_repository_repo}/branches?per_page=100&page={$this->page}");
$json = $response->json();
if ($response->status() !== 200) {

View File

@@ -213,7 +213,7 @@ class PublicGitRepository extends Component
return;
}
if ($this->git_source->getMorphClass() === 'App\Models\GithubApp') {
if ($this->git_source->getMorphClass() === \App\Models\GithubApp::class) {
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int) $this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branchFound = true;

View File

@@ -96,7 +96,6 @@ class Database extends Component
updateCompose($this->database);
$this->dispatch('success', 'Database saved.');
} catch (\Throwable $e) {
ray($e);
} finally {
$this->dispatch('generateDockerCompose');
}

View File

@@ -27,7 +27,6 @@ class Navbar extends Component
public function mount()
{
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
ray('isConfigurationChanged init');
$this->service->isConfigurationChanged(true);
$this->dispatch('configurationChanged');
}

View File

@@ -35,7 +35,7 @@ class All extends Component
public function mount()
{
$this->resourceClass = get_class($this->resource);
$resourceWithPreviews = ['App\Models\Application'];
$resourceWithPreviews = [\App\Models\Application::class];
$simpleDockerfile = ! is_null(data_get($this->resource, 'dockerfile'));
if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) {
$this->showPreview = true;

View File

@@ -58,7 +58,7 @@ class Show extends Component
public function mount()
{
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) {
$this->isSharedVariable = true;
}
$this->modalId = new Cuid2;
@@ -80,7 +80,7 @@ class Show extends Component
public function serialize()
{
data_forget($this->env, 'real_value');
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) {
data_forget($this->env, 'is_build_time');
}
}

View File

@@ -44,7 +44,7 @@ class GetLogs extends Component
public function mount()
{
if (! is_null($this->resource)) {
if ($this->resource->getMorphClass() === 'App\Models\Application') {
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
$this->showTimeStamps = $this->resource->settings->is_include_timestamps;
} else {
if ($this->servicesubtype) {
@@ -53,7 +53,7 @@ class GetLogs extends Component
$this->showTimeStamps = $this->resource->is_include_timestamps;
}
}
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
if ($this->resource?->getMorphClass() === \App\Models\Application::class) {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = 'Pull Request: '.str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
}
@@ -69,11 +69,11 @@ class GetLogs extends Component
public function instantSave()
{
if (! is_null($this->resource)) {
if ($this->resource->getMorphClass() === 'App\Models\Application') {
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
$this->resource->settings->is_include_timestamps = $this->showTimeStamps;
$this->resource->settings->save();
}
if ($this->resource->getMorphClass() === 'App\Models\Service') {
if ($this->resource->getMorphClass() === \App\Models\Service::class) {
$serviceName = str($this->container)->beforeLast('-')->value();
$subType = $this->resource->applications()->where('name', $serviceName)->first();
if ($subType) {
@@ -95,7 +95,7 @@ class GetLogs extends Component
if (! $this->server->isFunctional()) {
return;
}
if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) {
if (! $refresh && ($this->resource?->getMorphClass() === \App\Models\Service::class || str($this->container)->contains('-pr-'))) {
return;
}
if ($this->numberOfLines <= 0 || is_null($this->numberOfLines)) {

View File

@@ -109,7 +109,6 @@ class Logs extends Component
$this->containers = $this->containers->filter(function ($container) {
return str_contains($container, $this->query['pull_request_id']);
});
ray($this->containers);
}

View File

@@ -41,7 +41,7 @@ class ResourceOperations extends Component
}
$uuid = (string) new Cuid2;
$server = $new_destination->server;
if ($this->resource->getMorphClass() === 'App\Models\Application') {
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
$new_resource = $this->resource->replicate()->fill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
@@ -78,14 +78,14 @@ class ResourceOperations extends Component
return redirect()->to($route);
} elseif (
$this->resource->getMorphClass() === 'App\Models\StandalonePostgresql' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneMongodb' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneMysql' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneMariadb' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneRedis' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneKeydb' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneDragonfly' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneClickhouse'
$this->resource->getMorphClass() === \App\Models\StandalonePostgresql::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneMongodb::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneMysql::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneMariadb::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneRedis::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneKeydb::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneDragonfly::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class
) {
$uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate()->fill([

View File

@@ -83,7 +83,7 @@ class Add extends Component
]);
$this->file_storage_path = trim($this->file_storage_path);
$this->file_storage_path = str($this->file_storage_path)->start('/')->value();
if ($this->resource->getMorphClass() === 'App\Models\Application') {
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
$fs_path = application_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path;
}
LocalFileVolume::create(

View File

@@ -4,45 +4,82 @@ namespace App\Livewire\Server;
use App\Jobs\DockerCleanupJob;
use App\Models\Server;
use Livewire\Attributes\Rule;
use Livewire\Component;
class Advanced extends Component
{
public Server $server;
protected $rules = [
'server.settings.concurrent_builds' => 'required|integer|min:1',
'server.settings.dynamic_timeout' => 'required|integer|min:1',
'server.settings.force_docker_cleanup' => 'required|boolean',
'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
'server.settings.server_disk_usage_notification_threshold' => 'required|integer|min:50|max:100',
'server.settings.delete_unused_volumes' => 'boolean',
'server.settings.delete_unused_networks' => 'boolean',
];
public array $parameters = [];
protected $validationAttributes = [
#[Rule(['integer', 'min:1'])]
public int $concurrentBuilds = 1;
'server.settings.concurrent_builds' => 'Concurrent Builds',
'server.settings.dynamic_timeout' => 'Dynamic Timeout',
'server.settings.force_docker_cleanup' => 'Force Docker Cleanup',
'server.settings.docker_cleanup_frequency' => 'Docker Cleanup Frequency',
'server.settings.docker_cleanup_threshold' => 'Docker Cleanup Threshold',
'server.settings.server_disk_usage_notification_threshold' => 'Server Disk Usage Notification Threshold',
'server.settings.delete_unused_volumes' => 'Delete Unused Volumes',
'server.settings.delete_unused_networks' => 'Delete Unused Networks',
];
#[Rule(['integer', 'min:1'])]
public int $dynamicTimeout = 1;
#[Rule('boolean')]
public bool $forceDockerCleanup = false;
#[Rule('string')]
public string $dockerCleanupFrequency = '*/10 * * * *';
#[Rule(['integer', 'min:1', 'max:99'])]
public int $dockerCleanupThreshold = 10;
#[Rule(['integer', 'min:1', 'max:99'])]
public int $serverDiskUsageNotificationThreshold = 50;
#[Rule('boolean')]
public bool $deleteUnusedVolumes = false;
#[Rule('boolean')]
public bool $deleteUnusedNetworks = false;
public function mount(string $server_uuid)
{
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
$this->parameters = get_route_parameters();
$this->syncData();
} catch (\Throwable $e) {
return redirect()->route('server.show');
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->server->settings->concurrent_builds = $this->concurrentBuilds;
$this->server->settings->dynamic_timeout = $this->dynamicTimeout;
$this->server->settings->force_docker_cleanup = $this->forceDockerCleanup;
$this->server->settings->docker_cleanup_frequency = $this->dockerCleanupFrequency;
$this->server->settings->docker_cleanup_threshold = $this->dockerCleanupThreshold;
$this->server->settings->server_disk_usage_notification_threshold = $this->serverDiskUsageNotificationThreshold;
$this->server->settings->delete_unused_volumes = $this->deleteUnusedVolumes;
$this->server->settings->delete_unused_networks = $this->deleteUnusedNetworks;
$this->server->settings->save();
} else {
$this->concurrentBuilds = $this->server->settings->concurrent_builds;
$this->dynamicTimeout = $this->server->settings->dynamic_timeout;
$this->forceDockerCleanup = $this->server->settings->force_docker_cleanup;
$this->dockerCleanupFrequency = $this->server->settings->docker_cleanup_frequency;
$this->dockerCleanupThreshold = $this->server->settings->docker_cleanup_threshold;
$this->serverDiskUsageNotificationThreshold = $this->server->settings->server_disk_usage_notification_threshold;
$this->deleteUnusedVolumes = $this->server->settings->delete_unused_volumes;
$this->deleteUnusedNetworks = $this->server->settings->delete_unused_networks;
}
}
public function instantSave()
{
try {
$this->validate();
$this->server->settings->save();
$this->syncData(true);
$this->dispatch('success', 'Server updated.');
$this->dispatch('refreshServerShow');
// $this->dispatch('refreshServerShow');
} catch (\Throwable $e) {
$this->server->settings->refresh();
return handleError($e, $this);
}
}
@@ -60,12 +97,11 @@ class Advanced extends Component
public function submit()
{
try {
$frequency = $this->server->settings->docker_cleanup_frequency;
if (empty($frequency) || ! validate_cron_expression($frequency)) {
$this->server->settings->docker_cleanup_frequency = '*/10 * * * *';
throw new \Exception('Invalid Cron / Human expression for Docker Cleanup Frequency. Resetting to default 10 minutes.');
if (! validate_cron_expression($this->dockerCleanupFrequency)) {
$this->dockerCleanupFrequency = $this->server->settings->getOriginal('docker_cleanup_frequency');
throw new \Exception('Invalid Cron / Human expression for Docker Cleanup Frequency.');
}
$this->server->settings->save();
$this->syncData(true);
$this->dispatch('success', 'Server updated.');
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@@ -19,6 +19,15 @@ class Charts extends Component
public bool $poll = true;
public function mount(string $server_uuid)
{
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function pollData()
{
if ($this->poll || $this->interval <= 10) {

View File

@@ -3,27 +3,33 @@
namespace App\Livewire\Server;
use App\Models\Server;
use Livewire\Attributes\Rule;
use Livewire\Component;
class CloudflareTunnels extends Component
{
public Server $server;
protected $rules = [
'server.settings.is_cloudflare_tunnel' => 'required|boolean',
];
#[Rule(['required', 'boolean'])]
public bool $isCloudflareTunnelsEnabled;
protected $validationAttributes = [
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
];
public function mount(string $server_uuid)
{
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
$this->isCloudflareTunnelsEnabled = $this->server->settings->is_cloudflare_tunnel;
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSave()
{
try {
$this->validate();
$this->server->settings->is_cloudflare_tunnel = $this->isCloudflareTunnelsEnabled;
$this->server->settings->save();
$this->dispatch('success', 'Server updated.');
$this->dispatch('refreshServerShow');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -31,6 +37,7 @@ class CloudflareTunnels extends Component
public function manualCloudflareConfig()
{
$this->isCloudflareTunnelsEnabled = true;
$this->server->settings->is_cloudflare_tunnel = true;
$this->server->settings->save();
$this->server->refresh();

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Server;
use App\Actions\Server\DeleteServer;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
@@ -13,7 +14,16 @@ class Delete extends Component
{
use AuthorizesRequests;
public $server;
public Server $server;
public function mount(string $server_uuid)
{
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function delete($password)
{

View File

@@ -3,27 +3,87 @@
namespace App\Livewire\Server\Destination;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Support\Collection;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public Server $server;
public $parameters = [];
public Collection $networks;
public function mount()
public function mount(string $server_uuid)
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.index');
}
$this->networks = collect();
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
loggy($this->server);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
private function createNetworkAndAttachToProxy()
{
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
public function add($name)
{
if ($this->server->isSwarm()) {
$found = $this->server->swarmDockers()->where('network', $name)->first();
if ($found) {
$this->dispatch('error', 'Network already added to this server.');
return;
} else {
SwarmDocker::create([
'name' => $this->server->name.'-'.$name,
'network' => $this->name,
'server_id' => $this->server->id,
]);
}
} else {
$found = $this->server->standaloneDockers()->where('network', $name)->first();
if ($found) {
$this->dispatch('error', 'Network already added to this server.');
return;
} else {
StandaloneDocker::create([
'name' => $this->server->name.'-'.$name,
'network' => $name,
'server_id' => $this->server->id,
]);
}
$this->createNetworkAndAttachToProxy();
}
}
public function scan()
{
if ($this->server->isSwarm()) {
$alreadyAddedNetworks = $this->server->swarmDockers;
} else {
$alreadyAddedNetworks = $this->server->standaloneDockers;
}
$networks = instant_remote_process(['docker network ls --format "{{json .}}"'], $this->server, false);
$this->networks = format_docker_command_output_to_json($networks)->filter(function ($network) {
return $network['Name'] !== 'bridge' && $network['Name'] !== 'host' && $network['Name'] !== 'none';
})->filter(function ($network) use ($alreadyAddedNetworks) {
return ! $alreadyAddedNetworks->contains('network', $network['Name']);
});
if ($this->networks->count() === 0) {
$this->dispatch('success', 'No new destinations found on this server.');
return;
}
$this->dispatch('success', 'Scan done.');
}
public function render()
{
return view('livewire.server.destination.show');

View File

@@ -56,6 +56,7 @@ class Form extends Component
'server.settings.sentinel_push_interval_seconds' => 'required|integer|min:10',
'server.settings.sentinel_custom_url' => 'nullable|url',
'server.settings.is_sentinel_enabled' => 'required|boolean',
'server.settings.is_sentinel_debug_enabled' => 'required|boolean',
'server.settings.server_timezone' => 'required|string|timezone',
];
@@ -75,6 +76,7 @@ class Form extends Component
'server.settings.sentinel_metrics_history_days' => 'Metrics History',
'server.settings.sentinel_push_interval_seconds' => 'Push Interval',
'server.settings.is_sentinel_enabled' => 'Server API',
'server.settings.is_sentinel_debug_enabled' => 'Debug',
'server.settings.sentinel_custom_url' => 'Coolify URL',
'server.settings.server_timezone' => 'Server Timezone',
];
@@ -84,6 +86,7 @@ class Form extends Component
$this->server = $server;
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
$this->wildcard_domain = $this->server->settings->wildcard_domain;
}
public function checkSyncStatus()
@@ -108,7 +111,7 @@ class Form extends Component
{
if ($field === 'server.settings.docker_cleanup_frequency') {
$frequency = $this->server->settings->docker_cleanup_frequency;
if (empty($frequency) || ! validate_cron_expression($frequency)) {
if (! validate_cron_expression($frequency)) {
$this->dispatch('error', 'Invalid Cron / Human expression for Docker Cleanup Frequency. Resetting to default 10 minutes.');
$this->server->settings->docker_cleanup_frequency = '*/10 * * * *';
}
@@ -158,6 +161,11 @@ class Form extends Component
$this->restartSentinel();
}
public function updatedServerSettingsIsSentinelDebugEnabled()
{
$this->restartSentinel();
}
public function instantSave()
{
try {

View File

@@ -2,84 +2,96 @@
namespace App\Livewire\Server;
use App\Actions\Server\InstallLogDrain;
use App\Actions\Server\StartLogDrain;
use App\Actions\Server\StopLogDrain;
use App\Models\Server;
use Livewire\Attributes\Rule;
use Livewire\Component;
class LogDrains extends Component
{
public Server $server;
public $parameters = [];
#[Rule(['boolean'])]
public bool $isLogDrainNewRelicEnabled = false;
protected $rules = [
'server.settings.is_logdrain_newrelic_enabled' => 'required|boolean',
'server.settings.logdrain_newrelic_license_key' => 'required|string',
'server.settings.logdrain_newrelic_base_uri' => 'required|string',
'server.settings.is_logdrain_highlight_enabled' => 'required|boolean',
'server.settings.logdrain_highlight_project_id' => 'required|string',
'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
'server.settings.logdrain_axiom_dataset_name' => 'required|string',
'server.settings.logdrain_axiom_api_key' => 'required|string',
'server.settings.is_logdrain_custom_enabled' => 'required|boolean',
'server.settings.logdrain_custom_config' => 'required|string',
'server.settings.logdrain_custom_config_parser' => 'nullable',
];
#[Rule(['boolean'])]
public bool $isLogDrainCustomEnabled = false;
protected $validationAttributes = [
'server.settings.is_logdrain_newrelic_enabled' => 'New Relic log drain',
'server.settings.logdrain_newrelic_license_key' => 'New Relic license key',
'server.settings.logdrain_newrelic_base_uri' => 'New Relic base URI',
'server.settings.is_logdrain_highlight_enabled' => 'Highlight log drain',
'server.settings.logdrain_highlight_project_id' => 'Highlight project ID',
'server.settings.is_logdrain_axiom_enabled' => 'Axiom log drain',
'server.settings.logdrain_axiom_dataset_name' => 'Axiom dataset name',
'server.settings.logdrain_axiom_api_key' => 'Axiom API key',
'server.settings.is_logdrain_custom_enabled' => 'Custom log drain',
'server.settings.logdrain_custom_config' => 'Custom log drain configuration',
'server.settings.logdrain_custom_config_parser' => 'Custom log drain configuration parser',
];
#[Rule(['boolean'])]
public bool $isLogDrainAxiomEnabled = false;
public function mount()
#[Rule(['string', 'nullable'])]
public ?string $logDrainNewRelicLicenseKey = null;
#[Rule(['url', 'nullable'])]
public ?string $logDrainNewRelicBaseUri = null;
#[Rule(['string', 'nullable'])]
public ?string $logDrainAxiomDatasetName = null;
#[Rule(['string', 'nullable'])]
public ?string $logDrainAxiomApiKey = null;
#[Rule(['string', 'nullable'])]
public ?string $logDrainCustomConfig = null;
#[Rule(['string', 'nullable'])]
public ?string $logDrainCustomConfigParser = null;
public function mount(string $server_uuid)
{
$this->parameters = get_route_parameters();
try {
$server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($server)) {
return redirect()->route('server.index');
}
$this->server = $server;
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function configureLogDrain()
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$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;
$this->server->settings->logdrain_newrelic_license_key = $this->logDrainNewRelicLicenseKey;
$this->server->settings->logdrain_newrelic_base_uri = $this->logDrainNewRelicBaseUri;
$this->server->settings->logdrain_axiom_dataset_name = $this->logDrainAxiomDatasetName;
$this->server->settings->logdrain_axiom_api_key = $this->logDrainAxiomApiKey;
$this->server->settings->logdrain_custom_config = $this->logDrainCustomConfig;
$this->server->settings->logdrain_custom_config_parser = $this->logDrainCustomConfigParser;
$this->server->settings->save();
} else {
$this->isLogDrainNewRelicEnabled = $this->server->settings->is_logdrain_newrelic_enabled;
$this->isLogDrainAxiomEnabled = $this->server->settings->is_logdrain_axiom_enabled;
$this->isLogDrainCustomEnabled = $this->server->settings->is_logdrain_custom_enabled;
$this->logDrainNewRelicLicenseKey = $this->server->settings->logdrain_newrelic_license_key;
$this->logDrainNewRelicBaseUri = $this->server->settings->logdrain_newrelic_base_uri;
$this->logDrainAxiomDatasetName = $this->server->settings->logdrain_axiom_dataset_name;
$this->logDrainAxiomApiKey = $this->server->settings->logdrain_axiom_api_key;
$this->logDrainCustomConfig = $this->server->settings->logdrain_custom_config;
$this->logDrainCustomConfigParser = $this->server->settings->logdrain_custom_config_parser;
}
}
public function instantSave()
{
try {
InstallLogDrain::run($this->server);
if (! $this->server->isLogDrainEnabled()) {
$this->dispatch('serverRefresh');
$this->syncData(true);
if ($this->server->isLogDrainEnabled()) {
StartLogDrain::run($this->server);
$this->dispatch('success', 'Log drain service started.');
} else {
StopLogDrain::run($this->server);
$this->dispatch('success', 'Log drain service stopped.');
return;
}
$this->dispatch('serverRefresh');
$this->dispatch('success', 'Log drain service started.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSave(string $type)
{
try {
$ok = $this->submit($type);
if (! $ok) {
return;
}
$this->configureLogDrain();
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -88,79 +100,10 @@ class LogDrains extends Component
public function submit(string $type)
{
try {
$this->resetErrorBag();
if ($type === 'newrelic') {
$this->validate([
'server.settings.is_logdrain_newrelic_enabled' => 'required|boolean',
'server.settings.logdrain_newrelic_license_key' => 'required|string',
'server.settings.logdrain_newrelic_base_uri' => 'required|string',
]);
$this->server->settings->update([
'is_logdrain_highlight_enabled' => false,
'is_logdrain_axiom_enabled' => false,
'is_logdrain_custom_enabled' => false,
]);
} elseif ($type === 'highlight') {
$this->validate([
'server.settings.is_logdrain_highlight_enabled' => 'required|boolean',
'server.settings.logdrain_highlight_project_id' => 'required|string',
]);
$this->server->settings->update([
'is_logdrain_newrelic_enabled' => false,
'is_logdrain_axiom_enabled' => false,
'is_logdrain_custom_enabled' => false,
]);
} elseif ($type === 'axiom') {
$this->validate([
'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
'server.settings.logdrain_axiom_dataset_name' => 'required|string',
'server.settings.logdrain_axiom_api_key' => 'required|string',
]);
$this->server->settings->update([
'is_logdrain_newrelic_enabled' => false,
'is_logdrain_highlight_enabled' => false,
'is_logdrain_custom_enabled' => false,
]);
} elseif ($type === 'custom') {
$this->validate([
'server.settings.is_logdrain_custom_enabled' => 'required|boolean',
'server.settings.logdrain_custom_config' => 'required|string',
'server.settings.logdrain_custom_config_parser' => 'nullable',
]);
$this->server->settings->update([
'is_logdrain_newrelic_enabled' => false,
'is_logdrain_highlight_enabled' => false,
'is_logdrain_axiom_enabled' => false,
]);
}
if (! $this->server->isLogDrainEnabled()) {
StopLogDrain::dispatch($this->server);
}
$this->server->settings->save();
$this->syncData(true);
$this->dispatch('success', 'Settings saved.');
return true;
} catch (\Throwable $e) {
if ($type === 'newrelic') {
$this->server->settings->update([
'is_logdrain_newrelic_enabled' => false,
]);
} elseif ($type === 'highlight') {
$this->server->settings->update([
'is_logdrain_highlight_enabled' => false,
]);
} elseif ($type === 'axiom') {
$this->server->settings->update([
'is_logdrain_axiom_enabled' => false,
]);
} elseif ($type === 'custom') {
$this->server->settings->update([
'is_logdrain_custom_enabled' => false,
]);
}
handleError($e, $this);
return false;
return handleError($e, $this);
}
}

View File

@@ -8,26 +8,63 @@ use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public Server $server;
public $privateKeys = [];
public $parameters = [];
public function mount()
public function mount(string $server_uuid)
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.index');
}
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function setPrivateKey($privateKeyId)
{
$ownedPrivateKey = PrivateKey::ownedByCurrentTeam()->find($privateKeyId);
if (is_null($ownedPrivateKey)) {
$this->dispatch('error', 'You are not allowed to use this private key.');
return;
}
$originalPrivateKeyId = $this->server->getOriginal('private_key_id');
try {
$this->server->update(['private_key_id' => $privateKeyId]);
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(justCheckingNewKey: true);
if ($uptime) {
$this->dispatch('success', 'Private key updated successfully.');
} else {
throw new \Exception($error);
}
} catch (\Exception $e) {
$this->server->update(['private_key_id' => $originalPrivateKeyId]);
$this->server->validateConnection();
$this->dispatch('error', $e->getMessage());
}
}
public function checkConnection()
{
try {
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
} else {
$this->dispatch('error', 'Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$error);
return;
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.private-key.show');

View File

@@ -3,38 +3,203 @@
namespace App\Livewire\Server;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Rule;
use Livewire\Component;
class Show extends Component
{
use AuthorizesRequests;
public Server $server;
public array $parameters;
#[Rule(['required'])]
public string $name;
protected $listeners = ['refreshServerShow'];
#[Rule(['nullable'])]
public ?string $description;
public function mount()
#[Rule(['required'])]
public string $ip;
#[Rule(['required'])]
public string $user;
#[Rule(['required'])]
public string $port;
#[Rule(['nullable'])]
public ?string $validationLogs = null;
#[Rule(['nullable', 'url'])]
public ?string $wildcardDomain;
#[Rule(['required'])]
public bool $isReachable;
#[Rule(['required'])]
public bool $isUsable;
#[Rule(['required'])]
public bool $isSwarmManager;
#[Rule(['required'])]
public bool $isSwarmWorker;
#[Rule(['required'])]
public bool $isBuildServer;
#[Rule(['required'])]
public bool $isMetricsEnabled;
#[Rule(['required'])]
public string $sentinelToken;
#[Rule(['nullable'])]
public ?string $sentinelUpdatedAt;
#[Rule(['required', 'integer', 'min:1'])]
public int $sentinelMetricsRefreshRateSeconds;
#[Rule(['required', 'integer', 'min:1'])]
public int $sentinelMetricsHistoryDays;
#[Rule(['required', 'integer', 'min:10'])]
public int $sentinelPushIntervalSeconds;
#[Rule(['nullable', 'url'])]
public ?string $sentinelCustomUrl;
#[Rule(['required'])]
public bool $isSentinelEnabled;
#[Rule(['required'])]
public bool $isSentinelDebugEnabled;
#[Rule(['required'])]
public string $serverTimezone;
public array $timezones;
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},CloudflareTunnelConfigured" => '$refresh',
'refreshServerShow' => '$refresh',
];
}
public function mount(string $server_uuid)
{
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail();
$this->parameters = get_route_parameters();
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function refreshServerShow()
public function syncData(bool $toModel = false)
{
$this->server->refresh();
$this->dispatch('$refresh');
if ($toModel) {
$this->validate();
$this->server->name = $this->name;
$this->server->description = $this->description;
$this->server->ip = $this->ip;
$this->server->user = $this->user;
$this->server->port = $this->port;
$this->server->validation_logs = $this->validationLogs;
$this->server->save();
$this->server->settings->is_swarm_manager = $this->isSwarmManager;
$this->server->settings->is_swarm_worker = $this->isSwarmWorker;
$this->server->settings->is_build_server = $this->isBuildServer;
$this->server->settings->is_metrics_enabled = $this->isMetricsEnabled;
$this->server->settings->sentinel_token = $this->sentinelToken;
$this->server->settings->sentinel_metrics_refresh_rate_seconds = $this->sentinelMetricsRefreshRateSeconds;
$this->server->settings->sentinel_metrics_history_days = $this->sentinelMetricsHistoryDays;
$this->server->settings->sentinel_push_interval_seconds = $this->sentinelPushIntervalSeconds;
$this->server->settings->sentinel_custom_url = $this->sentinelCustomUrl;
$this->server->settings->is_sentinel_enabled = $this->isSentinelEnabled;
$this->server->settings->is_sentinel_debug_enabled = $this->isSentinelDebugEnabled;
$this->server->settings->server_timezone = $this->serverTimezone;
$this->server->settings->save();
} else {
$this->name = $this->server->name;
$this->description = $this->server->description;
$this->ip = $this->server->ip;
$this->user = $this->server->user;
$this->port = $this->server->port;
$this->wildcardDomain = $this->server->settings->wildcard_domain;
$this->isReachable = $this->server->settings->is_reachable;
$this->isUsable = $this->server->settings->is_usable;
$this->isSwarmManager = $this->server->settings->is_swarm_manager;
$this->isSwarmWorker = $this->server->settings->is_swarm_worker;
$this->isBuildServer = $this->server->settings->is_build_server;
$this->isMetricsEnabled = $this->server->settings->is_metrics_enabled;
$this->sentinelToken = $this->server->settings->sentinel_token;
$this->sentinelMetricsRefreshRateSeconds = $this->server->settings->sentinel_metrics_refresh_rate_seconds;
$this->sentinelMetricsHistoryDays = $this->server->settings->sentinel_metrics_history_days;
$this->sentinelPushIntervalSeconds = $this->server->settings->sentinel_push_interval_seconds;
$this->sentinelCustomUrl = $this->server->settings->sentinel_custom_url;
$this->isSentinelEnabled = $this->server->settings->is_sentinel_enabled;
$this->isSentinelDebugEnabled = $this->server->settings->is_sentinel_debug_enabled;
$this->sentinelUpdatedAt = $this->server->settings->updated_at;
$this->serverTimezone = $this->server->settings->server_timezone;
}
}
public function validateServer($install = true)
{
try {
$this->validationLogs = $this->server->validation_logs = null;
$this->server->save();
$this->dispatch('init', $install);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function checkLocalhostConnection()
{
$this->syncData(true);
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
$this->server->settings->is_reachable = $this->isReachable = true;
$this->server->settings->is_usable = $this->isUsable = true;
$this->server->settings->save();
$this->dispatch('proxyStatusUpdated');
} else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error);
return;
}
}
public function regenerateSentinelToken()
{
try {
$this->server->settings->generateSentinelToken();
$this->dispatch('success', 'Token regenerated & Sentinel restarted.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSave()
{
$this->submit();
}
public function submit()
{
$this->dispatch('serverRefresh', false);
try {
$this->syncData(true);
$this->dispatch('success', 'Server updated');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()

View File

@@ -1,68 +0,0 @@
<?php
namespace App\Livewire\Server;
use App\Models\PrivateKey;
use App\Models\Server;
use Livewire\Component;
class ShowPrivateKey extends Component
{
public Server $server;
public $privateKeys;
public $parameters;
public function mount()
{
$this->parameters = get_route_parameters();
}
public function setPrivateKey($privateKeyId)
{
$ownedPrivateKey = PrivateKey::ownedByCurrentTeam()->find($privateKeyId);
if (is_null($ownedPrivateKey)) {
$this->dispatch('error', 'You are not allowed to use this private key.');
return;
}
$originalPrivateKeyId = $this->server->getOriginal('private_key_id');
try {
$this->server->update(['private_key_id' => $privateKeyId]);
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Private key updated successfully.');
} else {
throw new \Exception('Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$error);
}
} catch (\Exception $e) {
$this->server->update(['private_key_id' => $originalPrivateKeyId]);
$this->server->validateConnection();
$this->dispatch('error', 'Failed to update private key: '.$e->getMessage());
} finally {
$this->dispatch('refreshServerShow');
$this->server->refresh();
}
}
public function checkConnection()
{
try {
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
} else {
$this->dispatch('error', 'Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$error);
return;
}
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refreshServerShow');
$this->server->refresh();
}
}
}

View File

@@ -7,70 +7,93 @@ use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Rule;
use Livewire\Component;
class Index extends Component
{
public InstanceSettings $settings;
public bool $do_not_track;
public bool $is_auto_update_enabled;
public bool $is_registration_enabled;
public bool $is_dns_validation_enabled;
public bool $is_api_enabled;
public string $auto_update_frequency;
public string $update_check_frequency;
public $timezones;
public bool $disable_two_step_confirmation;
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
protected Server $server;
protected $rules = [
'settings.fqdn' => 'nullable',
'settings.resale_license' => 'nullable',
'settings.public_port_min' => 'required',
'settings.public_port_max' => 'required',
'settings.custom_dns_servers' => 'nullable',
'settings.instance_name' => 'nullable',
'settings.allowed_ips' => 'nullable',
'settings.is_auto_update_enabled' => 'boolean',
'settings.public_ipv4' => 'nullable',
'settings.public_ipv6' => 'nullable',
'auto_update_frequency' => 'string',
'update_check_frequency' => 'string',
'settings.instance_timezone' => 'required|string|timezone',
];
#[Locked]
public $timezones;
protected $validationAttributes = [
'settings.fqdn' => 'FQDN',
'settings.resale_license' => 'Resale License',
'settings.public_port_min' => 'Public port min',
'settings.public_port_max' => 'Public port max',
'settings.custom_dns_servers' => 'Custom DNS servers',
'settings.allowed_ips' => 'Allowed IPs',
'settings.is_auto_update_enabled' => 'Auto Update Enabled',
'settings.public_ipv4' => 'IPv4',
'settings.public_ipv6' => 'IPv6',
'auto_update_frequency' => 'Auto Update Frequency',
'update_check_frequency' => 'Update Check Frequency',
'settings.instance_timezone' => 'Instance Timezone',
];
#[Rule('boolean')]
public bool $is_auto_update_enabled;
#[Rule('nullable|string|max:255')]
public ?string $fqdn = null;
#[Rule('nullable|string|max:255')]
public ?string $resale_license = null;
#[Rule('required|integer|min:1025|max:65535')]
public int $public_port_min;
#[Rule('required|integer|min:1025|max:65535')]
public int $public_port_max;
#[Rule('nullable|string')]
public ?string $custom_dns_servers = null;
#[Rule('nullable|string|max:255')]
public ?string $instance_name = null;
#[Rule('nullable|string')]
public ?string $allowed_ips = null;
#[Rule('nullable|string')]
public ?string $public_ipv4 = null;
#[Rule('nullable|string')]
public ?string $public_ipv6 = null;
#[Rule('string')]
public string $auto_update_frequency;
#[Rule('string')]
public string $update_check_frequency;
#[Rule('required|string|timezone')]
public string $instance_timezone;
#[Rule('boolean')]
public bool $do_not_track;
#[Rule('boolean')]
public bool $is_registration_enabled;
#[Rule('boolean')]
public bool $is_dns_validation_enabled;
#[Rule('boolean')]
public bool $is_api_enabled;
#[Rule('boolean')]
public bool $disable_two_step_confirmation;
public function render()
{
return view('livewire.settings.index');
}
public function mount()
{
if (isInstanceAdmin()) {
if (! isInstanceAdmin()) {
return redirect()->route('dashboard');
} else {
$this->settings = instanceSettings();
loggy($this->settings);
$this->fqdn = $this->settings->fqdn;
$this->resale_license = $this->settings->resale_license;
$this->public_port_min = $this->settings->public_port_min;
$this->public_port_max = $this->settings->public_port_max;
$this->custom_dns_servers = $this->settings->custom_dns_servers;
$this->instance_name = $this->settings->instance_name;
$this->allowed_ips = $this->settings->allowed_ips;
$this->public_ipv4 = $this->settings->public_ipv4;
$this->public_ipv6 = $this->settings->public_ipv6;
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
@@ -79,14 +102,22 @@ class Index extends Component
$this->auto_update_frequency = $this->settings->auto_update_frequency;
$this->update_check_frequency = $this->settings->update_check_frequency;
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
$this->instance_timezone = $this->settings->instance_timezone;
$this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation;
} else {
return redirect()->route('dashboard');
}
}
public function instantSave()
public function instantSave($isSave = true)
{
$this->settings->fqdn = $this->fqdn;
$this->settings->resale_license = $this->resale_license;
$this->settings->public_port_min = $this->public_port_min;
$this->settings->public_port_max = $this->public_port_max;
$this->settings->custom_dns_servers = $this->custom_dns_servers;
$this->settings->instance_name = $this->instance_name;
$this->settings->allowed_ips = $this->allowed_ips;
$this->settings->public_ipv4 = $this->public_ipv4;
$this->settings->public_ipv6 = $this->public_ipv6;
$this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
@@ -95,8 +126,11 @@ class Index extends Component
$this->settings->auto_update_frequency = $this->auto_update_frequency;
$this->settings->update_check_frequency = $this->update_check_frequency;
$this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation;
$this->settings->save();
$this->dispatch('success', 'Settings updated!');
$this->settings->instance_timezone = $this->instance_timezone;
if ($isSave) {
$this->settings->save();
$this->dispatch('success', 'Settings updated!');
}
}
public function submit()
@@ -153,13 +187,8 @@ class Index extends Component
$this->settings->allowed_ips = $this->settings->allowed_ips->unique();
$this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
$this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
$this->settings->is_api_enabled = $this->is_api_enabled;
$this->settings->auto_update_frequency = $this->auto_update_frequency;
$this->settings->update_check_frequency = $this->update_check_frequency;
$this->instantSave(isSave: false);
$this->settings->save();
$this->server->setupDynamicProxyConfiguration();
if (! $error_show) {
@@ -182,11 +211,6 @@ class Index extends Component
}
}
public function render()
{
return view('livewire.settings.index');
}
public function toggleTwoStepConfirmation($password)
{
if (! Hash::check($password, Auth::user()->password)) {
@@ -195,9 +219,8 @@ class Index extends Component
return;
}
$this->settings->disable_two_step_confirmation = true;
$this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation = true;
$this->settings->save();
$this->disable_two_step_confirmation = true;
$this->dispatch('success', 'Two step confirmation has been disabled.');
}
}

View File

@@ -50,7 +50,6 @@ class License extends Component
$this->dispatch('reloadWindow');
} catch (\Throwable $e) {
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: '.$e->getMessage());
ray($e->getMessage());
return redirect()->route('settings.license');
}

View File

@@ -2,50 +2,59 @@
namespace App\Livewire;
use App\Jobs\DatabaseBackupJob;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Rule;
use Livewire\Component;
class SettingsBackup extends Component
{
public InstanceSettings $settings;
public $s3s;
public ?StandalonePostgresql $database = null;
public ScheduledDatabaseBackup|null|array $backup = [];
#[Locked]
public $s3s;
#[Locked]
public $executions = [];
protected $rules = [
'database.uuid' => 'required',
'database.name' => 'required',
'database.description' => 'nullable',
'database.postgres_user' => 'required',
'database.postgres_password' => 'required',
#[Rule(['required'])]
public string $uuid;
];
#[Rule(['required'])]
public string $name;
protected $validationAttributes = [
'database.uuid' => 'uuid',
'database.name' => 'name',
'database.description' => 'description',
'database.postgres_user' => 'postgres user',
'database.postgres_password' => 'postgres password',
];
#[Rule(['nullable'])]
public ?string $description = null;
#[Rule(['required'])]
public string $postgres_user;
#[Rule(['required'])]
public string $postgres_password;
public function mount()
{
if (isInstanceAdmin()) {
if (! isInstanceAdmin()) {
return redirect()->route('dashboard');
} else {
$settings = instanceSettings();
$this->database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($this->database) {
$this->uuid = $this->database->uuid;
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->postgres_user = $this->database->postgres_user;
$this->postgres_password = $this->database->postgres_password;
if ($this->database->status !== 'running') {
$this->database->status = 'running';
$this->database->save();
@@ -55,13 +64,10 @@ class SettingsBackup extends Component
}
$this->settings = $settings;
$this->s3s = $s3s;
} else {
return redirect()->route('dashboard');
}
}
public function add_coolify_database()
public function addCoolifyDatabase()
{
try {
$server = Server::findOrFail(0);
@@ -78,7 +84,7 @@ class SettingsBackup extends Component
'postgres_password' => $postgres_password,
'postgres_db' => $postgres_db,
'status' => 'running',
'destination_type' => 'App\Models\StandaloneDocker',
'destination_type' => \App\Models\StandaloneDocker::class,
'destination_id' => 0,
]);
$this->backup = ScheduledDatabaseBackup::create([
@@ -87,7 +93,7 @@ class SettingsBackup extends Component
'save_s3' => false,
'frequency' => '0 0 * * *',
'database_id' => $this->database->id,
'database_type' => 'App\Models\StandalonePostgresql',
'database_type' => \App\Models\StandalonePostgresql::class,
'team_id' => currentTeam()->id,
]);
$this->database->refresh();
@@ -98,16 +104,14 @@ class SettingsBackup extends Component
}
}
public function backup_now()
{
dispatch(new DatabaseBackupJob(
backup: $this->backup
));
$this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
}
public function submit()
{
$this->database->update([
'name' => $this->name,
'description' => $this->description,
'postgres_user' => $this->postgres_user,
'postgres_password' => $this->postgres_password,
]);
$this->dispatch('success', 'Backup updated.');
}
}

View File

@@ -59,20 +59,16 @@ class AdminView extends Component
foreach ($servers as $server) {
$resources = $server->definedResources();
foreach ($resources as $resource) {
ray('Deleting resource: '.$resource->name);
$resource->forceDelete();
}
ray('Deleting server: '.$server->name);
$server->forceDelete();
}
$projects = $team->projects;
foreach ($projects as $project) {
ray('Deleting project: '.$project->name);
$project->forceDelete();
}
$team->members()->detach($user->id);
ray('Deleting team: '.$team->name);
$team->delete();
}
@@ -91,29 +87,23 @@ class AdminView extends Component
$user = User::find($id);
$teams = $user->teams;
foreach ($teams as $team) {
ray($team->name);
$user_alone_in_team = $team->members->count() === 1;
if ($team->id === 0) {
if ($user_alone_in_team) {
ray('user is alone in the root team, do nothing');
return $this->dispatch('error', 'User is alone in the root team, cannot delete');
}
}
if ($user_alone_in_team) {
ray('user is alone in the team');
$this->finalizeDeletion($user, $team);
continue;
}
ray('user is not alone in the team');
if ($user->isOwner()) {
$found_other_owner_or_admin = $team->members->filter(function ($member) {
return $member->pivot->role === 'owner' || $member->pivot->role === 'admin';
})->where('id', '!=', $user->id)->first();
if ($found_other_owner_or_admin) {
ray('found other owner or admin');
$team->members()->detach($user->id);
continue;
@@ -122,24 +112,19 @@ class AdminView extends Component
return $member->pivot->role === 'member';
})->first();
if ($found_other_member_who_is_not_owner) {
ray('found other member who is not owner');
$found_other_member_who_is_not_owner->pivot->role = 'owner';
$found_other_member_who_is_not_owner->pivot->save();
$team->members()->detach($user->id);
} else {
// This should never happen as if the user is the only member in the team, the team should be deleted already.
ray('found no other member who is not owner');
$this->finalizeDeletion($user, $team);
}
continue;
}
} else {
ray('user is not owner');
$team->members()->detach($user->id);
}
}
ray('Deleting user: '.$user->name);
$user->delete();
$this->getUsers();
}

View File

@@ -2,41 +2,77 @@
namespace App\Livewire\Team;
use App\Enums\Role;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Livewire\Attributes\Locked;
use Livewire\Component;
class Member extends Component
{
#[Locked]
public User $member;
public function makeAdmin()
{
$this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'admin']);
$this->dispatch('reloadWindow');
try {
if (Role::from(auth()->user()->role())->lt(Role::ADMIN)
|| Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
throw new \Exception('You are not authorized to perform this action.');
}
$this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => Role::ADMIN->value]);
$this->dispatch('reloadWindow');
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
}
}
public function makeOwner()
{
$this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'owner']);
$this->dispatch('reloadWindow');
try {
if (Role::from(auth()->user()->role())->lt(Role::OWNER)
|| Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
throw new \Exception('You are not authorized to perform this action.');
}
$this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => Role::OWNER->value]);
$this->dispatch('reloadWindow');
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
}
}
public function makeReadonly()
{
$this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'member']);
$this->dispatch('reloadWindow');
try {
if (Role::from(auth()->user()->role())->lt(Role::ADMIN)
|| Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
throw new \Exception('You are not authorized to perform this action.');
}
$this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => Role::MEMBER->value]);
$this->dispatch('reloadWindow');
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
}
}
public function remove()
{
$this->member->teams()->detach(currentTeam());
Cache::forget("team:{$this->member->id}");
Cache::remember('team:'.$this->member->id, 3600, function () {
return $this->member->teams()->first();
});
$this->dispatch('reloadWindow');
try {
if (Role::from(auth()->user()->role())->lt(Role::ADMIN)
|| Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
throw new \Exception('You are not authorized to perform this action.');
}
$this->member->teams()->detach(currentTeam());
Cache::forget("team:{$this->member->id}");
Cache::remember('team:'.$this->member->id, 3600, function () {
return $this->member->teams()->first();
});
$this->dispatch('reloadWindow');
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
}
}
private function getMemberRole()
{
return $this->member->teams()->where('teams.id', currentTeam()->id)->first()?->pivot?->role;
}
}

View File

@@ -17,8 +17,6 @@ class VerifyEmail extends Component
$this->dispatch('success', 'Email verification link sent!');
} catch (\Exception $e) {
ray($e);
return handleError($e, $this);
}
}