Merge branch 'coollabsio:main' into fix-postgres-init-scripts

This commit is contained in:
🏔️ Peak
2024-12-04 13:15:58 +01:00
committed by GitHub
735 changed files with 29218 additions and 11181 deletions

View File

@@ -3,24 +3,17 @@
namespace App\Livewire\Project;
use App\Models\Project;
use Livewire\Attributes\Validate;
use Livewire\Component;
class AddEmpty extends Component
{
public string $name = '';
#[Validate(['required', 'string', 'min:3'])]
public string $name;
#[Validate(['nullable', 'string'])]
public string $description = '';
protected $rules = [
'name' => 'required|string|min:3',
'description' => 'nullable|string',
];
protected $validationAttributes = [
'name' => 'Project Name',
'description' => 'Project Description',
];
public function submit()
{
try {
@@ -34,8 +27,6 @@ class AddEmpty extends Component
return redirect()->route('project.show', $project->uuid);
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->name = '';
}
}
}

View File

@@ -1,44 +0,0 @@
<?php
namespace App\Livewire\Project;
use App\Models\Environment;
use App\Models\Project;
use Livewire\Component;
class AddEnvironment extends Component
{
public Project $project;
public string $name = '';
public string $description = '';
protected $rules = [
'name' => 'required|string|min:3',
];
protected $validationAttributes = [
'name' => 'Environment Name',
];
public function submit()
{
try {
$this->validate();
$environment = Environment::create([
'name' => $this->name,
'project_id' => $this->project->id,
]);
return redirect()->route('project.resource.index', [
'project_uuid' => $this->project->uuid,
'environment_name' => $environment->name,
]);
} catch (\Throwable $e) {
handleError($e, $this);
} finally {
$this->name = '';
}
}
}

View File

@@ -3,120 +3,200 @@
namespace App\Livewire\Project\Application;
use App\Models\Application;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Advanced extends Component
{
public Application $application;
public bool $is_force_https_enabled;
#[Validate(['boolean'])]
public bool $isForceHttpsEnabled = false;
public bool $is_gzip_enabled;
#[Validate(['boolean'])]
public bool $isGitSubmodulesEnabled = false;
public bool $is_stripprefix_enabled;
#[Validate(['boolean'])]
public bool $isGitLfsEnabled = false;
protected $rules = [
'application.settings.is_git_submodules_enabled' => 'boolean|required',
'application.settings.is_git_lfs_enabled' => 'boolean|required',
'application.settings.is_preview_deployments_enabled' => 'boolean|required',
'application.settings.is_auto_deploy_enabled' => 'boolean|required',
'is_force_https_enabled' => 'boolean|required',
'application.settings.is_log_drain_enabled' => 'boolean|required',
'application.settings.is_gpu_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
'application.settings.custom_internal_name' => 'string|nullable',
'application.settings.is_gzip_enabled' => 'boolean|required',
'application.settings.is_stripprefix_enabled' => 'boolean|required',
'application.settings.gpu_driver' => 'string|required',
'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required',
'application.settings.gpu_options' => 'string|required',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.connect_to_docker_network' => 'boolean|required',
];
#[Validate(['boolean'])]
public bool $isPreviewDeploymentsEnabled = false;
#[Validate(['boolean'])]
public bool $isAutoDeployEnabled = true;
#[Validate(['boolean'])]
public bool $isLogDrainEnabled = false;
#[Validate(['boolean'])]
public bool $isGpuEnabled = false;
#[Validate(['string'])]
public string $gpuDriver = '';
#[Validate(['string', 'nullable'])]
public ?string $gpuCount = null;
#[Validate(['string', 'nullable'])]
public ?string $gpuDeviceIds = null;
#[Validate(['string', 'nullable'])]
public ?string $gpuOptions = null;
#[Validate(['boolean'])]
public bool $isBuildServerEnabled = false;
#[Validate(['boolean'])]
public bool $isConsistentContainerNameEnabled = false;
#[Validate(['string', 'nullable'])]
public ?string $customInternalName = null;
#[Validate(['boolean'])]
public bool $isGzipEnabled = true;
#[Validate(['boolean'])]
public bool $isStripprefixEnabled = true;
#[Validate(['boolean'])]
public bool $isRawComposeDeploymentEnabled = false;
#[Validate(['boolean'])]
public bool $isConnectToDockerNetworkEnabled = false;
public function mount()
{
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
$this->is_gzip_enabled = $this->application->isGzipEnabled();
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
try {
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->application->settings->is_force_https_enabled = $this->isForceHttpsEnabled;
$this->application->settings->is_git_submodules_enabled = $this->isGitSubmodulesEnabled;
$this->application->settings->is_git_lfs_enabled = $this->isGitLfsEnabled;
$this->application->settings->is_preview_deployments_enabled = $this->isPreviewDeploymentsEnabled;
$this->application->settings->is_auto_deploy_enabled = $this->isAutoDeployEnabled;
$this->application->settings->is_log_drain_enabled = $this->isLogDrainEnabled;
$this->application->settings->is_gpu_enabled = $this->isGpuEnabled;
$this->application->settings->gpu_driver = $this->gpuDriver;
$this->application->settings->gpu_count = $this->gpuCount;
$this->application->settings->gpu_device_ids = $this->gpuDeviceIds;
$this->application->settings->gpu_options = $this->gpuOptions;
$this->application->settings->is_build_server_enabled = $this->isBuildServerEnabled;
$this->application->settings->is_consistent_container_name_enabled = $this->isConsistentContainerNameEnabled;
$this->application->settings->custom_internal_name = $this->customInternalName;
$this->application->settings->is_gzip_enabled = $this->isGzipEnabled;
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
$this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled;
$this->application->settings->save();
} else {
$this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled();
$this->isGzipEnabled = $this->application->isGzipEnabled();
$this->isStripprefixEnabled = $this->application->isStripprefixEnabled();
$this->isLogDrainEnabled = $this->application->isLogDrainEnabled();
$this->isGitSubmodulesEnabled = $this->application->settings->is_git_submodules_enabled;
$this->isGitLfsEnabled = $this->application->settings->is_git_lfs_enabled;
$this->isPreviewDeploymentsEnabled = $this->application->settings->is_preview_deployments_enabled;
$this->isAutoDeployEnabled = $this->application->settings->is_auto_deploy_enabled;
$this->isGpuEnabled = $this->application->settings->is_gpu_enabled;
$this->gpuDriver = $this->application->settings->gpu_driver;
$this->gpuCount = $this->application->settings->gpu_count;
$this->gpuDeviceIds = $this->application->settings->gpu_device_ids;
$this->gpuOptions = $this->application->settings->gpu_options;
$this->isBuildServerEnabled = $this->application->settings->is_build_server_enabled;
$this->isConsistentContainerNameEnabled = $this->application->settings->is_consistent_container_name_enabled;
$this->customInternalName = $this->application->settings->custom_internal_name;
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
}
}
public function instantSave()
{
if ($this->application->isLogDrainEnabled()) {
if (! $this->application->destination->server->isLogDrainEnabled()) {
$this->application->settings->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on this server.');
try {
if ($this->isLogDrainEnabled) {
if (! $this->application->destination->server->isLogDrainEnabled()) {
$this->isLogDrainEnabled = false;
$this->syncData(true);
$this->dispatch('error', 'Log drain is not enabled on this server.');
return;
return;
}
}
if ($this->application->isForceHttpsEnabled() !== $this->isForceHttpsEnabled ||
$this->application->isGzipEnabled() !== $this->isGzipEnabled ||
$this->application->isStripprefixEnabled() !== $this->isStripprefixEnabled
) {
$this->dispatch('resetDefaultLabels', false);
}
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->oldRawParser();
} else {
$this->application->parse();
}
$this->syncData(true);
$this->dispatch('success', 'Settings saved.');
$this->dispatch('configurationChanged');
} catch (\Throwable $e) {
return handleError($e, $this);
}
if ($this->application->settings->is_force_https_enabled !== $this->is_force_https_enabled) {
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
$this->dispatch('resetDefaultLabels', false);
}
if ($this->application->settings->is_gzip_enabled !== $this->is_gzip_enabled) {
$this->application->settings->is_gzip_enabled = $this->is_gzip_enabled;
$this->dispatch('resetDefaultLabels', false);
}
if ($this->application->settings->is_stripprefix_enabled !== $this->is_stripprefix_enabled) {
$this->application->settings->is_stripprefix_enabled = $this->is_stripprefix_enabled;
$this->dispatch('resetDefaultLabels', false);
}
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->oldRawParser();
} else {
$this->application->parse();
}
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
$this->dispatch('configurationChanged');
}
public function submit()
{
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
$this->application->settings->gpu_count = null;
$this->application->settings->gpu_device_ids = null;
$this->application->settings->save();
try {
if ($this->gpuCount && $this->gpuDeviceIds) {
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
$this->gpuCount = null;
$this->gpuDeviceIds = null;
$this->syncData(true);
return;
return;
}
$this->syncData(true);
$this->dispatch('success', 'Settings saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
}
public function saveCustomName()
{
if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
if (str($this->customInternalName)->isNotEmpty()) {
$this->customInternalName = str($this->customInternalName)->slug()->value();
} else {
$this->application->settings->custom_internal_name = null;
$this->customInternalName = null;
}
if (is_null($this->application->settings->custom_internal_name)) {
$this->application->settings->save();
if (is_null($this->customInternalName)) {
$this->syncData(true);
$this->dispatch('success', 'Custom name saved.');
return;
}
$customInternalName = $this->application->settings->custom_internal_name;
$customInternalName = $this->customInternalName;
$server = $this->application->destination->server;
$allApplications = $server->applications();
$foundSameInternalName = $allApplications->filter(function ($application) {
return $application->id !== $this->application->id && $application->settings->custom_internal_name === $this->application->settings->custom_internal_name;
return $application->id !== $this->application->id && $application->settings->custom_internal_name === $this->customInternalName;
});
if ($foundSameInternalName->isNotEmpty()) {
$this->dispatch('error', 'This custom container name is already in use by another application on this server.');
$this->application->settings->custom_internal_name = $customInternalName;
$this->application->settings->refresh();
$this->customInternalName = $customInternalName;
$this->syncData(true);
return;
}
$this->application->settings->save();
$this->syncData(true);
$this->dispatch('success', 'Custom name saved.');
}

View File

@@ -16,24 +16,28 @@ class Configuration extends Component
public function mount()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (! $project) {
return redirect()->route('dashboard');
$this->application = Application::query()
->whereHas('environment.project', function ($query) {
$query->where('team_id', currentTeam()->id)
->where('uuid', request()->route('project_uuid'));
})
->whereHas('environment', function ($query) {
$query->where('name', request()->route('environment_name'));
})
->where('uuid', request()->route('application_uuid'))
->with(['destination' => function ($query) {
$query->select('id', 'server_id');
}])
->firstOrFail();
if ($this->application->destination && $this->application->destination->server_id) {
$this->servers = Server::ownedByCurrentTeam()
->select('id', 'name')
->where('id', '!=', $this->application->destination->server_id)
->get();
} else {
$this->servers = collect();
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (! $environment) {
return redirect()->route('dashboard');
}
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
if (! $application) {
return redirect()->route('dashboard');
}
$this->application = $application;
$mainServer = $this->application->destination->server;
$servers = Server::ownedByCurrentTeam()->get();
$this->servers = $servers->filter(function ($server) use ($mainServer) {
return $server->id != $mainServer->id;
});
}
public function render()

View File

@@ -64,7 +64,7 @@ class Show extends Component
{
$this->dispatch('deploymentFinished');
$this->application_deployment_queue->refresh();
if (data_get($this->application_deployment_queue, 'status') == 'finished' || data_get($this->application_deployment_queue, 'status') == 'failed') {
if (data_get($this->application_deployment_queue, 'status') === 'finished' || data_get($this->application_deployment_queue, 'status') === 'failed') {
$this->isKeepAliveOn = false;
}
}

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

@@ -84,6 +84,7 @@ class General extends Component
'application.pre_deployment_command_container' => 'nullable',
'application.post_deployment_command' => 'nullable',
'application.post_deployment_command_container' => 'nullable',
'application.custom_nginx_configuration' => 'nullable',
'application.settings.is_static' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
@@ -121,6 +122,7 @@ class General extends Component
'application.custom_docker_run_options' => 'Custom docker run commands',
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
'application.custom_nginx_configuration' => 'Custom Nginx configuration',
'application.settings.is_static' => 'Is static',
'application.settings.is_build_server_enabled' => 'Is build server enabled',
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
@@ -241,6 +243,12 @@ class General extends Component
}
}
public function updatedApplicationSettingsIsStatic($value)
{
if ($value) {
$this->generateNginxConfiguration();
}
}
public function updatedApplicationBuildPack()
{
@@ -258,6 +266,7 @@ class General extends Component
if ($this->application->build_pack === 'static') {
$this->application->ports_exposes = $this->ports_exposes = 80;
$this->resetDefaultLabels(false);
$this->generateNginxConfiguration();
}
$this->submit();
$this->dispatch('buildPackUpdated');
@@ -275,10 +284,17 @@ class General extends Component
}
}
public function resetDefaultLabels()
public function generateNginxConfiguration()
{
$this->application->custom_nginx_configuration = defaultNginxConfiguration();
$this->application->save();
$this->dispatch('success', 'Nginx configuration generated.');
}
public function resetDefaultLabels($manualReset = false)
{
try {
if ($this->application->settings->is_container_label_readonly_enabled) {
if ($this->application->settings->is_container_label_readonly_enabled && ! $manualReset) {
return;
}
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
@@ -314,7 +330,7 @@ class General extends Component
public function set_redirect()
{
try {
$has_www = collect($this->application->fqdns)->filter(fn($fqdn) => str($fqdn)->contains('www.'))->count();
$has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
if ($has_www === 0 && $this->application->redirect === 'www') {
$this->dispatch('error', 'You want to redirect to www, but you do not have a www domain set.<br><br>Please add www to your domain list and as an A DNS record (if applicable).');
@@ -335,9 +351,15 @@ class General extends Component
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
Url::fromString($domain, ['http', 'https']);
return str($domain)->trim()->lower();
});
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$warning = sslipDomainWarning($this->application->fqdn);
if ($warning) {
$this->dispatch('warning', __('warning.sslipdomain'));
}
$this->resetDefaultLabels();
if ($this->application->isDirty('redirect')) {
@@ -403,17 +425,19 @@ class General extends Component
}
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
$showToaster && $this->dispatch('success', 'Application settings updated!');
$showToaster && ! $warning && $this->dispatch('success', 'Application settings updated!');
} catch (\Throwable $e) {
$originalFqdn = $this->application->getOriginal('fqdn');
if ($originalFqdn !== $this->application->fqdn) {
$this->application->fqdn = $originalFqdn;
}
return handleError($e, $this);
} finally {
$this->dispatch('configurationChanged');
}
}
public function downloadConfig()
{
$config = GenerateConfig::run($this->application, true);
@@ -423,7 +447,7 @@ class General extends Component
echo $config;
}, $fileName, [
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename=' . $fileName,
'Content-Disposition' => 'attachment; filename='.$fileName,
]);
}
}

View File

@@ -36,7 +36,11 @@ class Heading extends Component
public function mount()
{
$this->parameters = get_route_parameters();
$this->parameters = [
'project_uuid' => $this->application->project()->uuid,
'environment_name' => $this->application->environment->name,
'application_uuid' => $this->application->uuid,
];
$lastDeployment = $this->application->get_last_successful_deployment();
$this->lastDeploymentInfo = data_get_str($lastDeployment, 'commit')->limit(7).' '.data_get($lastDeployment, 'commit_message');
$this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit'));
@@ -45,13 +49,11 @@ class Heading extends Component
public function check_status($showNotification = false)
{
if ($this->application->destination->server->isFunctional()) {
GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high');
GetContainersStatus::dispatch($this->application->destination->server);
}
if ($showNotification) {
$this->dispatch('success', 'Success', 'Application status updated.');
}
// Removed because it caused flickering
// $this->dispatch('configurationChanged');
}
public function force_deploy_without_cache()

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Application\Preview;
use App\Models\Application;
use Livewire\Attributes\Validate;
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 $e) {
$this->dispatch('error', 'Invalid FQDN.');
}
}
}
#[Validate('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

@@ -5,6 +5,7 @@ namespace App\Livewire\Project\Application;
use App\Actions\Docker\GetContainersStatus;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Carbon\Carbon;
use Illuminate\Process\InvokedProcess;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Process;
@@ -239,7 +240,7 @@ class Previews extends Component
$processes[$containerName] = $this->stopContainer($containerName, $timeout);
}
$startTime = time();
$startTime = Carbon::now()->getTimestamp();
while (count($processes) > 0) {
$finishedProcesses = array_filter($processes, function ($process) {
return ! $process->running();
@@ -249,7 +250,7 @@ class Previews extends Component
$this->removeContainer($containerName, $server);
}
if (time() - $startTime >= $timeout) {
if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
$this->forceStopRemainingContainers(array_keys($processes), $server);
break;
}

View File

@@ -4,55 +4,92 @@ namespace App\Livewire\Project\Application;
use App\Models\Application;
use App\Models\PrivateKey;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Source extends Component
{
public $applicationId;
public Application $application;
public $private_keys;
#[Locked]
public $privateKeys;
protected $rules = [
'application.git_repository' => 'required',
'application.git_branch' => 'required',
'application.git_commit_sha' => 'nullable',
];
#[Validate(['nullable', 'string'])]
public ?string $privateKeyName = null;
protected $validationAttributes = [
'application.git_repository' => 'repository',
'application.git_branch' => 'branch',
'application.git_commit_sha' => 'commit sha',
];
#[Validate(['nullable', 'integer'])]
public ?int $privateKeyId = null;
#[Validate(['required', 'string'])]
public string $gitRepository;
#[Validate(['required', 'string'])]
public string $gitBranch;
#[Validate(['nullable', 'string'])]
public ?string $gitCommitSha = null;
public function mount()
{
$this->get_private_keys();
try {
$this->syncData();
$this->getPrivateKeys();
} catch (\Throwable $e) {
handleError($e, $this);
}
}
private function get_private_keys()
public function syncData(bool $toModel = false)
{
$this->private_keys = PrivateKey::whereTeamId(currentTeam()->id)->get()->reject(function ($key) {
return $key->id == $this->application->private_key_id;
if ($toModel) {
$this->validate();
$this->application->update([
'git_repository' => $this->gitRepository,
'git_branch' => $this->gitBranch,
'git_commit_sha' => $this->gitCommitSha,
'private_key_id' => $this->privateKeyId,
]);
} else {
$this->gitRepository = $this->application->git_repository;
$this->gitBranch = $this->application->git_branch;
$this->gitCommitSha = $this->application->git_commit_sha;
$this->privateKeyId = $this->application->private_key_id;
$this->privateKeyName = data_get($this->application, 'private_key.name');
}
}
private function getPrivateKeys()
{
$this->privateKeys = PrivateKey::whereTeamId(currentTeam()->id)->get()->reject(function ($key) {
return $key->id == $this->privateKeyId;
});
}
public function setPrivateKey(int $private_key_id)
public function setPrivateKey(int $privateKeyId)
{
$this->application->private_key_id = $private_key_id;
$this->application->save();
$this->application->refresh();
$this->get_private_keys();
try {
$this->privateKeyId = $privateKeyId;
$this->syncData(true);
$this->getPrivateKeys();
$this->application->refresh();
$this->privateKeyName = $this->application->private_key->name;
$this->dispatch('success', 'Private key updated!');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit()
{
$this->validate();
if (! $this->application->git_commit_sha) {
$this->application->git_commit_sha = 'HEAD';
try {
if (str($this->gitCommitSha)->isEmpty()) {
$this->gitCommitSha = 'HEAD';
}
$this->syncData(true);
$this->dispatch('success', 'Application source updated!');
} catch (\Throwable $e) {
return handleError($e, $this);
}
$this->application->save();
$this->dispatch('success', 'Application source updated!');
}
}

View File

@@ -3,32 +3,55 @@
namespace App\Livewire\Project\Application;
use App\Models\Application;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Swarm extends Component
{
public Application $application;
public string $swarm_placement_constraints = '';
#[Validate('required')]
public int $swarmReplicas;
protected $rules = [
'application.swarm_replicas' => 'required',
'application.swarm_placement_constraints' => 'nullable',
'application.settings.is_swarm_only_worker_nodes' => 'required',
];
#[Validate(['nullable'])]
public ?string $swarmPlacementConstraints = null;
#[Validate('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

@@ -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

@@ -2,66 +2,100 @@
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\Validate;
use Livewire\Component;
use Spatie\Url\Url;
class BackupEdit extends Component
{
public ?ScheduledDatabaseBackup $backup;
public ScheduledDatabaseBackup $backup;
#[Locked]
public $s3s;
#[Locked]
public $parameters;
#[Validate(['required', 'boolean'])]
public bool $delete_associated_backups_locally = false;
#[Validate(['required', 'boolean'])]
public bool $delete_associated_backups_s3 = false;
#[Validate(['required', 'boolean'])]
public bool $delete_associated_backups_sftp = false;
#[Validate(['nullable', 'string'])]
public ?string $status = null;
public array $parameters;
#[Validate(['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',
];
#[Validate(['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',
];
#[Validate(['required', 'integer', 'min:1'])]
public int $numberOfBackupsLocally = 1;
protected $messages = [
'backup.s3_storage_id' => 'Select a S3 Storage',
];
#[Validate(['required', 'boolean'])]
public bool $saveS3 = false;
#[Validate(['nullable', 'integer'])]
public ?int $s3StorageId = 1;
#[Validate(['nullable', 'string'])]
public ?string $databasesToBackup = null;
#[Validate(['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;
}
}
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
return;
}
}
try {
@@ -74,7 +108,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');
@@ -93,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;
@@ -117,25 +149,20 @@ 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;
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;
@@ -149,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

@@ -2,10 +2,10 @@
namespace App\Livewire\Project\Database;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Attributes\On;
use Livewire\Component;
class BackupExecutions extends Component
@@ -28,7 +28,6 @@ class BackupExecutions extends Component
return [
"echo-private:team.{$userId},BackupCreated" => 'refreshBackupExecutions',
'deleteBackup',
];
}
@@ -41,13 +40,14 @@ class BackupExecutions extends Component
}
}
#[On('deleteBackup')]
public function deleteBackup($executionId, $password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
return;
}
}
$execution = $this->backup->executions()->where('id', $executionId)->first();
@@ -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);
@@ -119,9 +119,8 @@ class BackupExecutions extends Component
if (! $server) {
return 'UTC';
}
$serverTimezone = $server->settings->server_timezone;
return $serverTimezone;
return $server->settings->server_timezone;
}
public function formatDateInServerTimezone($date)
@@ -130,7 +129,7 @@ class BackupExecutions extends Component
$dateObj = new \DateTime($date);
try {
$dateObj->setTimezone(new \DateTimeZone($serverTimezone));
} catch (\Exception $e) {
} catch (\Exception) {
$dateObj->setTimezone(new \DateTimeZone('UTC'));
}

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\Validate;
use Livewire\Component;
class General extends Component
@@ -15,54 +17,106 @@ class General extends Component
public StandaloneClickhouse $database;
public ?string $db_url = null;
#[Validate(['required', 'string'])]
public string $name;
public ?string $db_url_public = null;
#[Validate(['nullable', 'string'])]
public ?string $description = null;
protected $listeners = ['refresh'];
#[Validate(['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',
];
#[Validate(['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',
];
#[Validate(['required', 'string'])]
public string $image;
#[Validate(['nullable', 'string'])]
public ?string $portsMappings = null;
#[Validate(['nullable', 'boolean'])]
public ?bool $isPublic = null;
#[Validate(['nullable', 'integer'])]
public ?int $publicPort = null;
#[Validate(['nullable', 'string'])]
public ?string $customDockerRunOptions = null;
#[Validate(['nullable', 'string'])]
public ?string $dbUrl = null;
#[Validate(['nullable', 'string'])]
public ?string $dbUrlPublic = null;
#[Validate(['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,59 +4,62 @@ namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Illuminate\Support\Collection;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class CreateScheduledBackup extends Component
{
public $database;
#[Validate(['required', 'string'])]
public $frequency;
#[Validate(['required', 'boolean'])]
public bool $saveToS3 = false;
#[Locked]
public $database;
public bool $enabled = true;
public bool $save_s3 = false;
#[Validate(['nullable', 'integer'])]
public ?int $s3StorageId = null;
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.');
return;
}
$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,
];
if ($this->database->type() === 'standalone-postgresql') {
$payload['databases_to_backup'] = $this->database->postgres_db;
} elseif ($this->database->type() === 'standalone-mysql') {
@@ -66,16 +69,16 @@ 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');
}
} catch (\Throwable $e) {
handleError($e, $this);
return handleError($e, $this);
} finally {
$this->frequency = '';
$this->save_s3 = 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\Validate;
use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneDragonfly $database;
public ?string $db_url = null;
#[Validate(['required', 'string'])]
public string $name;
public ?string $db_url_public = null;
#[Validate(['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',
];
#[Validate(['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',
];
#[Validate(['required', 'string'])]
public string $image;
#[Validate(['nullable', 'string'])]
public ?string $portsMappings = null;
#[Validate(['nullable', 'boolean'])]
public ?bool $isPublic = null;
#[Validate(['nullable', 'integer'])]
public ?int $publicPort = null;
#[Validate(['nullable', 'string'])]
public ?string $customDockerRunOptions = null;
#[Validate(['nullable', 'string'])]
public ?string $dbUrl = null;
#[Validate(['nullable', 'string'])]
public ?string $dbUrlPublic = null;
#[Validate(['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',
@@ -77,10 +78,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;
}
@@ -88,8 +89,7 @@ class Import extends Component
public function runImport()
{
if ($this->filename == '') {
if ($this->filename === '') {
$this->dispatch('error', 'Please select a file to import.');
return;
@@ -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

@@ -3,39 +3,39 @@
namespace App\Livewire\Project\Database;
use Exception;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class InitScript extends Component
{
#[Locked]
public array $script;
#[Locked]
public int $index;
public ?string $filename;
#[Validate(['nullable', 'string'])]
public ?string $filename = null;
public ?string $content;
protected $rules = [
'filename' => 'required|string',
'content' => 'required|string',
];
protected $validationAttributes = [
'filename' => 'Filename',
'content' => 'Content',
];
#[Validate(['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,63 +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\Validate;
use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneKeydb $database;
public ?string $db_url = null;
#[Validate(['required', 'string'])]
public string $name;
public ?string $db_url_public = null;
#[Validate(['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',
];
#[Validate(['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',
];
#[Validate(['required', 'string'])]
public string $keydbPassword;
#[Validate(['required', 'string'])]
public string $image;
#[Validate(['nullable', 'string'])]
public ?string $portsMappings = null;
#[Validate(['nullable', 'boolean'])]
public ?bool $isPublic = null;
#[Validate(['nullable', 'integer'])]
public ?int $publicPort = null;
#[Validate(['nullable', 'string'])]
public ?string $customDockerRunOptions = null;
#[Validate(['nullable', 'string'])]
public ?string $dbUrl = null;
#[Validate(['nullable', 'string'])]
public ?string $dbUrlPublic = null;
#[Validate(['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) {
@@ -71,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);
@@ -90,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

@@ -57,7 +57,6 @@ class General extends Component
$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');
}
public function instantSaveAdvanced()

View File

@@ -55,7 +55,6 @@ class General extends Component
$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');
}
public function instantSaveAdvanced()

View File

@@ -11,12 +11,21 @@ use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
protected $listeners = [
'envsUpdated' => 'refresh',
'refresh',
];
public Server $server;
public StandaloneRedis $database;
public string $redis_username;
public string $redis_password;
public string $redis_version;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -25,33 +34,33 @@ class General extends Component
'database.name' => 'required',
'database.description' => 'nullable',
'database.redis_conf' => 'nullable',
'database.redis_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',
'redis_username' => 'required',
'redis_password' => 'required',
];
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.redis_conf' => 'Redis Configuration',
'database.redis_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 Options',
'redis_username' => 'Redis Username',
'redis_password' => 'Redis Password',
];
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');
$this->refreshView();
}
public function instantSaveAdvanced()
@@ -75,13 +84,24 @@ class General extends Component
{
try {
$this->validate();
if ($this->database->redis_conf === '') {
$this->database->redis_conf = null;
if (version_compare($this->redis_version, '6.0', '>=')) {
$this->database->runtime_environment_variables()->updateOrCreate(
['key' => 'REDIS_USERNAME'],
['value' => $this->redis_username, 'standalone_redis_id' => $this->database->id]
);
}
$this->database->runtime_environment_variables()->updateOrCreate(
['key' => 'REDIS_PASSWORD'],
['value' => $this->redis_password, 'standalone_redis_id' => $this->database->id]
);
$this->database->save();
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refreshEnvs');
}
}
@@ -119,10 +139,25 @@ class General extends Component
public function refresh(): void
{
$this->database->refresh();
$this->refreshView();
}
private function refreshView()
{
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
$this->redis_version = $this->database->getRedisVersion();
$this->redis_username = $this->database->redis_username;
$this->redis_password = $this->database->redis_password;
}
public function render()
{
return view('livewire.project.database.redis.general');
}
public function isSharedVariable($name)
{
return $this->database->runtime_environment_variables()->where('key', $name)->where('is_shared', true)->exists();
}
}

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

@@ -7,18 +7,22 @@ use Livewire\Component;
class DeleteEnvironment extends Component
{
public array $parameters;
public int $environment_id;
public bool $disabled = false;
public string $environmentName = '';
public array $parameters;
public function mount()
{
$this->parameters = get_route_parameters();
$this->environmentName = Environment::findOrFail($this->environment_id)->name;
try {
$this->environmentName = Environment::findOrFail($this->environment_id)->name;
$this->parameters = get_route_parameters();
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function delete()
@@ -30,9 +34,9 @@ class DeleteEnvironment extends Component
if ($environment->isEmpty()) {
$environment->delete();
return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]);
return redirect()->route('project.show', parameters: ['project_uuid' => $this->parameters['project_uuid']]);
}
return $this->dispatch('error', 'Environment has defined resources, please delete them first.');
return $this->dispatch('error', "<strong>Environment {$environment->name}</strong> has defined resources, please delete them first.");
}
}

View File

@@ -27,11 +27,12 @@ class DeleteProject extends Component
'project_id' => 'required|int',
]);
$project = Project::findOrFail($this->project_id);
if ($project->applications->count() > 0) {
return $this->dispatch('error', 'Project has resources defined, please delete them first.');
}
$project->delete();
if ($project->isEmpty()) {
$project->delete();
return redirect()->route('project.index');
return redirect()->route('project.index');
}
return $this->dispatch('error', "<strong>Project {$project->name}</strong> has resources defined, please delete them first.");
}
}

View File

@@ -3,34 +3,47 @@
namespace App\Livewire\Project;
use App\Models\Project;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Edit extends Component
{
public Project $project;
protected $rules = [
'project.name' => 'required|min:3|max:255',
'project.description' => 'nullable|string|max:255',
];
#[Validate(['required', 'string', 'min:3', 'max:255'])]
public string $name;
public function mount()
#[Validate(['nullable', 'string', 'max:255'])]
public ?string $description = null;
public function mount(string $project_uuid)
{
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (! $project) {
return redirect()->route('dashboard');
try {
$this->project = Project::where('team_id', currentTeam()->id)->where('uuid', $project_uuid)->firstOrFail();
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->project->update([
'name' => $this->name,
'description' => $this->description,
]);
} else {
$this->name = $this->project->name;
$this->description = $this->project->description;
}
$this->project = $project;
}
public function submit()
{
try {
$this->validate();
$this->project->save();
$this->dispatch('saved');
$this->syncData(true);
$this->dispatch('success', 'Project updated.');
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@@ -4,6 +4,8 @@ namespace App\Livewire\Project;
use App\Models\Application;
use App\Models\Project;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class EnvironmentEdit extends Component
@@ -12,29 +14,45 @@ class EnvironmentEdit extends Component
public Application $application;
#[Locked]
public $environment;
public array $parameters;
#[Validate(['required', 'string', 'min:3', 'max:255'])]
public string $name;
protected $rules = [
'environment.name' => 'required|min:3|max:255',
'environment.description' => 'nullable|min:3|max:255',
];
#[Validate(['nullable', 'string', 'max:255'])]
public ?string $description = null;
public function mount()
public function mount(string $project_uuid, string $environment_name)
{
$this->parameters = get_route_parameters();
$this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first();
$this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first();
try {
$this->project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail();
$this->environment = $this->project->environments()->where('name', $environment_name)->firstOrFail();
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->environment->update([
'name' => $this->name,
'description' => $this->description,
]);
} else {
$this->name = $this->environment->name;
$this->description = $this->environment->description;
}
}
public function submit()
{
$this->validate();
try {
$this->environment->save();
return redirect()->route('project.environment.edit', ['project_uuid' => $this->project->uuid, 'environment_name' => $this->environment->name]);
$this->syncData(true);
$this->redirectRoute('project.environment.edit', ['environment_name' => $this->environment->name, 'project_uuid' => $this->project->uuid]);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -18,7 +18,11 @@ class Index extends Component
public function mount()
{
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get()->map(function ($project) {
$project->settingsRoute = route('project.edit', ['project_uuid' => $project->uuid]);
return $project;
});
$this->servers = Server::ownedByCurrentTeam()->count();
}

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

@@ -198,7 +198,7 @@ class GithubPrivateRepositoryDeployKey extends Component
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
if ($this->git_host == 'github.com') {
if ($this->git_host === 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
return;

View File

@@ -99,7 +99,6 @@ class PublicGitRepository extends Component
$this->base_directory = '/'.$this->base_directory;
}
}
}
public function updatedDockerComposeLocation()
@@ -174,7 +173,7 @@ class PublicGitRepository extends Component
return;
}
if (! $this->branchFound && $this->git_branch == 'main') {
if (! $this->branchFound && $this->git_branch === 'main') {
try {
$this->git_branch = 'master';
$this->getBranch();
@@ -197,7 +196,7 @@ class PublicGitRepository extends Component
} else {
$this->git_branch = 'main';
}
if ($this->git_host == 'github.com') {
if ($this->git_host === 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
return;
@@ -213,7 +212,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;
@@ -317,6 +316,7 @@ class PublicGitRepository extends Component
// $application->setConfig($config);
// }
}
return redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid,
'environment_name' => $environment->name,

File diff suppressed because one or more lines are too long

View File

@@ -100,7 +100,6 @@ class Create extends Component
'is_preview' => false,
]);
}
});
}
$service->parse(isNew: true);

View File

@@ -32,8 +32,11 @@ class Index extends Component
public $services = [];
public array $parameters;
public function mount()
{
$this->parameters = get_route_parameters();
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (! $project) {
return redirect()->route('dashboard');
@@ -44,7 +47,6 @@ class Index extends Component
}
$this->project = $project;
$this->environment = $environment;
$this->applications = $this->environment->applications->load(['tags']);
$this->applications = $this->applications->map(function ($application) {
if (data_get($application, 'environment.project.uuid')) {

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

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

View File

@@ -21,6 +21,7 @@ class EditDomain extends Component
{
$this->application = ServiceApplication::find($this->applicationId);
}
public function submit()
{
try {
@@ -28,9 +29,14 @@ class EditDomain extends Component
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
Url::fromString($domain, ['http', 'https']);
return str($domain)->trim()->lower();
});
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$warning = sslipDomainWarning($this->application->fqdn);
if ($warning) {
$this->dispatch('warning', __('warning.sslipdomain'));
}
check_domain_usage(resource: $this->application);
$this->validate();
$this->application->save();
@@ -38,7 +44,7 @@ class EditDomain extends Component
if (str($this->application->fqdn)->contains(',')) {
$this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.');
} else {
$this->dispatch('success', 'Service saved.');
! $warning && $this->dispatch('success', 'Service saved.');
}
$this->application->service->parse();
$this->dispatch('refresh');
@@ -48,6 +54,7 @@ class EditDomain extends Component
if ($originalFqdn !== $this->application->fqdn) {
$this->application->fqdn = $originalFqdn;
}
return handleError($e, $this);
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Service;
use App\Models\Application;
use App\Models\InstanceSettings;
use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -87,10 +88,12 @@ class FileStorage extends Component
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
return;
}
}
try {

View File

@@ -48,7 +48,6 @@ class Index extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function generateDockerCompose()

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;
@@ -27,7 +28,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');
}
@@ -35,10 +35,11 @@ class Navbar extends Component
public function getListeners()
{
$userId = auth()->user()->id;
$userId = Auth::id();
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',
'envsUpdated' => '$refresh',
];
}
@@ -76,7 +77,7 @@ class Navbar extends Component
} else {
$this->isDeploymentProgress = false;
}
} catch (\Throwable $e) {
} catch (\Throwable) {
$this->isDeploymentProgress = false;
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Service;
use App\Models\InstanceSettings;
use App\Models\ServiceApplication;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
@@ -30,11 +31,6 @@ class ServiceApplicationView extends Component
'application.is_stripprefix_enabled' => 'nullable|boolean',
];
public function updatedApplicationFqdn()
{
}
public function instantSave()
{
$this->submit();
@@ -54,10 +50,12 @@ class ServiceApplicationView extends Component
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
return;
}
}
try {
@@ -82,10 +80,14 @@ class ServiceApplicationView extends Component
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
Url::fromString($domain, ['http', 'https']);
return str($domain)->trim()->lower();
});
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$warning = sslipDomainWarning($this->application->fqdn);
if ($warning) {
$this->dispatch('warning', __('warning.sslipdomain'));
}
check_domain_usage(resource: $this->application);
$this->validate();
$this->application->save();
@@ -93,7 +95,7 @@ class ServiceApplicationView extends Component
if (str($this->application->fqdn)->contains(',')) {
$this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.');
} else {
$this->dispatch('success', 'Service saved.');
! $warning && $this->dispatch('success', 'Service saved.');
}
$this->dispatch('generateDockerCompose');
} catch (\Throwable $e) {
@@ -101,6 +103,7 @@ class ServiceApplicationView extends Component
if ($originalFqdn !== $this->application->fqdn) {
$this->application->fqdn = $originalFqdn;
}
return handleError($e, $this);
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Shared;
use App\Jobs\DeleteResourceJob;
use App\Models\InstanceSettings;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -61,37 +62,26 @@ class Danger extends Component
return;
}
switch ($this->resource->type()) {
case 'application':
$this->resourceName = $this->resource->name ?? 'Application';
break;
case 'standalone-postgresql':
case 'standalone-redis':
case 'standalone-mongodb':
case 'standalone-mysql':
case 'standalone-mariadb':
case 'standalone-keydb':
case 'standalone-dragonfly':
case 'standalone-clickhouse':
$this->resourceName = $this->resource->name ?? 'Database';
break;
case 'service':
$this->resourceName = $this->resource->name ?? 'Service';
break;
case 'service-application':
$this->resourceName = $this->resource->name ?? 'Service Application';
break;
case 'service-database':
$this->resourceName = $this->resource->name ?? 'Service Database';
break;
default:
$this->resourceName = 'Unknown Resource';
}
$this->resourceName = match ($this->resource->type()) {
'application' => $this->resource->name ?? 'Application',
'standalone-postgresql',
'standalone-redis',
'standalone-mongodb',
'standalone-mysql',
'standalone-mariadb',
'standalone-keydb',
'standalone-dragonfly',
'standalone-clickhouse' => $this->resource->name ?? 'Database',
'service' => $this->resource->name ?? 'Service',
'service-application' => $this->resource->name ?? 'Service Application',
'service-database' => $this->resource->name ?? 'Service Database',
default => 'Unknown Resource',
};
}
public function delete($password)
{
if (isProduction()) {
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');

View File

@@ -5,7 +5,7 @@ namespace App\Livewire\Project\Shared;
use App\Actions\Application\StopApplicationOneServer;
use App\Actions\Docker\GetContainersStatus;
use App\Events\ApplicationStatusChanged;
use App\Jobs\ContainerStatusJob;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\StandaloneDocker;
use Illuminate\Support\Facades\Auth;
@@ -119,10 +119,12 @@ class Destination extends Component
public function removeServer(int $network_id, int $server_id, $password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
return;
}
}
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {

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

@@ -37,6 +37,7 @@ class Show extends Component
'env.is_literal' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable',
'env.is_required' => 'required|boolean',
];
protected $validationAttributes = [
@@ -46,6 +47,7 @@ class Show extends Component
'env.is_multiline' => 'Multiline',
'env.is_literal' => 'Literal',
'env.is_shown_once' => 'Shown Once',
'env.is_required' => 'Required',
];
public function refresh()
@@ -56,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;
@@ -78,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');
}
}
@@ -109,15 +111,21 @@ class Show extends Component
} else {
$this->validate();
}
// if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) {
// $type = str($this->env->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// return;
// }
// }
if (! $this->isSharedVariable && $this->env->is_required && str($this->env->real_value)->isEmpty()) {
$oldValue = $this->env->getOriginal('value');
$this->env->value = $oldValue;
$this->dispatch('error', 'Required environment variable cannot be empty.');
return;
}
$this->serialize();
if ($this->isSharedVariable) {
unset($this->env->is_required);
}
$this->env->save();
$this->dispatch('success', 'Environment variable updated.');
$this->dispatch('envsUpdated');

View File

@@ -52,6 +52,7 @@ class ExecuteContainerCommand extends Component
$this->servers = $this->servers->push($server);
}
}
$this->loadContainers();
} elseif (data_get($this->parameters, 'database_uuid')) {
$this->type = 'database';
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id'));
@@ -62,12 +63,18 @@ class ExecuteContainerCommand extends Component
if ($this->resource->destination->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->destination->server);
}
$this->loadContainers();
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}
$this->loadContainers();
} elseif (data_get($this->parameters, 'server_uuid')) {
$this->type = 'server';
$this->resource = Server::where('uuid', $this->parameters['server_uuid'])->firstOrFail();
$this->server = $this->resource;
}
}
@@ -125,11 +132,31 @@ class ExecuteContainerCommand extends Component
}
});
}
}
if ($this->containers->count() > 0) {
$this->container = $this->containers->first();
}
if ($this->containers->count() === 1) {
$this->selected_container = data_get($this->containers->first(), 'container.Names');
}
}
#[On('connectToServer')]
public function connectToServer()
{
try {
if ($this->server->isForceDisabled()) {
throw new \RuntimeException('Server is disabled.');
}
$this->dispatch(
'send-terminal-command',
false,
data_get($this->server, 'name'),
data_get($this->server, 'uuid')
);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
#[On('connectToContainer')]
@@ -156,7 +183,6 @@ class ExecuteContainerCommand extends Component
data_get($container, 'container.Names'),
data_get($container, 'server.uuid')
);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -39,12 +39,12 @@ class GetLogs extends Component
public ?bool $showTimeStamps = true;
public int $numberOfLines = 100;
public ?int $numberOfLines = 100;
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,10 +95,10 @@ 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) {
if ($this->numberOfLines <= 0 || is_null($this->numberOfLines)) {
$this->numberOfLines = 1000;
}
if ($this->container) {

View File

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

View File

@@ -31,13 +31,8 @@ class Metrics extends Component
public function loadData()
{
try {
$metrics = $this->resource->getMetrics($this->interval);
$cpuMetrics = collect($metrics)->map(function ($metric) {
return [$metric[0], $metric[1]];
});
$memoryMetrics = collect($metrics)->map(function ($metric) {
return [$metric[0], $metric[2]];
});
$cpuMetrics = $this->resource->getCpuMetrics($this->interval);
$memoryMetrics = $this->resource->getMemoryMetrics($this->interval);
$this->dispatch("refreshChartData-{$this->chartId}-cpu", [
'seriesData' => $cpuMetrics,
]);

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([
@@ -147,7 +147,6 @@ class ResourceOperations extends Component
return redirect()->to($route);
}
}
public function moveTo($environment_id)

View File

@@ -55,8 +55,8 @@ class Add extends Component
return;
}
if (empty($this->container) || $this->container == 'null') {
if ($this->type == 'service') {
if (empty($this->container) || $this->container === 'null') {
if ($this->type === 'service') {
$this->container = $this->subServiceName;
}
}

View File

@@ -21,10 +21,10 @@ class All extends Component
public function mount()
{
$this->parameters = get_route_parameters();
if ($this->resource->type() == 'service') {
if ($this->resource->type() === 'service') {
$this->containerNames = $this->resource->applications()->pluck('name');
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
} elseif ($this->resource->type() == 'application') {
} elseif ($this->resource->type() === 'application') {
if ($this->resource->build_pack === 'dockercompose') {
$parsed = $this->resource->parse();
$containers = collect(data_get($parsed, 'services'))->keys();

View File

@@ -2,23 +2,60 @@
namespace App\Livewire\Project\Shared\ScheduledTask;
use App\Models\ScheduledTask;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Locked;
use Livewire\Component;
class Executions extends Component
{
public $executions = [];
public ScheduledTask $task;
public $selectedKey;
#[Locked]
public int $taskId;
public $task;
#[Locked]
public Collection $executions;
#[Locked]
public ?int $selectedKey = null;
#[Locked]
public ?string $serverTimezone = null;
public function getListeners()
{
$teamId = Auth::user()->currentTeam()->id;
return [
'selectTask',
"echo-private:team.{$teamId},ScheduledTaskDone" => 'refreshExecutions',
];
}
public function mount($taskId)
{
try {
$this->taskId = $taskId;
$this->task = ScheduledTask::findOrFail($taskId);
$this->executions = $this->task->executions()->take(20)->get();
$this->serverTimezone = data_get($this->task, 'application.destination.server.settings.server_timezone');
if (! $this->serverTimezone) {
$this->serverTimezone = data_get($this->task, 'service.destination.server.settings.server_timezone');
}
if (! $this->serverTimezone) {
$this->serverTimezone = 'UTC';
}
} catch (\Exception $e) {
return handleError($e);
}
}
public function refreshExecutions(): void
{
$this->executions = $this->task->executions()->take(20)->get();
}
public function selectTask($key): void
{
if ($key == $this->selectedKey) {
@@ -29,43 +66,13 @@ class Executions extends Component
$this->selectedKey = $key;
}
public function server()
{
if (! $this->task) {
return null;
}
if ($this->task->application) {
if ($this->task->application->destination && $this->task->application->destination->server) {
return $this->task->application->destination->server;
}
} elseif ($this->task->service) {
if ($this->task->service->destination && $this->task->service->destination->server) {
return $this->task->service->destination->server;
}
}
return null;
}
public function getServerTimezone()
{
$server = $this->server();
if (! $server) {
return 'UTC';
}
$serverTimezone = $server->settings->server_timezone;
return $serverTimezone;
}
public function formatDateInServerTimezone($date)
{
$serverTimezone = $this->getServerTimezone();
$serverTimezone = $this->serverTimezone;
$dateObj = new \DateTime($date);
try {
$dateObj->setTimezone(new \DateTimeZone($serverTimezone));
} catch (\Exception $e) {
} catch (\Exception) {
$dateObj->setTimezone(new \DateTimeZone('UTC'));
}

View File

@@ -2,74 +2,124 @@
namespace App\Livewire\Project\Shared\ScheduledTask;
use App\Jobs\ScheduledTaskJob;
use App\Models\Application;
use App\Models\ScheduledTask as ModelsScheduledTask;
use App\Models\ScheduledTask;
use App\Models\Service;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Show extends Component
{
public $parameters;
public Application|Service $resource;
public ModelsScheduledTask $task;
public ScheduledTask $task;
public ?string $modalId = null;
#[Locked]
public array $parameters;
#[Locked]
public string $type;
public string $scheduledTaskName;
#[Validate(['boolean'])]
public bool $isEnabled = false;
protected $rules = [
'task.enabled' => 'required|boolean',
'task.name' => 'required|string',
'task.command' => 'required|string',
'task.frequency' => 'required|string',
'task.container' => 'nullable|string',
];
#[Validate(['string', 'required'])]
public string $name;
protected $validationAttributes = [
'name' => 'name',
'command' => 'command',
'frequency' => 'frequency',
'container' => 'container',
];
#[Validate(['string', 'required'])]
public string $command;
public function mount()
#[Validate(['string', 'required'])]
public string $frequency;
#[Validate(['string', 'nullable'])]
public ?string $container = null;
#[Locked]
public ?string $application_uuid;
#[Locked]
public ?string $service_uuid;
#[Locked]
public string $task_uuid;
public function mount(string $task_uuid, string $project_uuid, string $environment_name, ?string $application_uuid = null, ?string $service_uuid = null)
{
$this->parameters = get_route_parameters();
try {
$this->task_uuid = $task_uuid;
if ($application_uuid) {
$this->type = 'application';
$this->application_uuid = $application_uuid;
$this->resource = Application::ownedByCurrentTeam()->where('uuid', $application_uuid)->firstOrFail();
} elseif ($service_uuid) {
$this->type = 'service';
$this->service_uuid = $service_uuid;
$this->resource = Service::ownedByCurrentTeam()->where('uuid', $service_uuid)->firstOrFail();
}
$this->parameters = [
'environment_name' => $environment_name,
'project_uuid' => $project_uuid,
'application_uuid' => $application_uuid,
'service_uuid' => $service_uuid,
];
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->task = $this->resource->scheduled_tasks()->where('uuid', $task_uuid)->firstOrFail();
$this->syncData();
} catch (\Exception $e) {
return handleError($e);
}
}
$this->modalId = new Cuid2;
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
$this->scheduledTaskName = $this->task->name;
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->task->enabled = $this->isEnabled;
$this->task->name = str($this->name)->trim()->value();
$this->task->command = str($this->command)->trim()->value();
$this->task->frequency = str($this->frequency)->trim()->value();
$this->task->container = str($this->container)->trim()->value();
$this->task->save();
} else {
$this->isEnabled = $this->task->enabled;
$this->name = $this->task->name;
$this->command = $this->task->command;
$this->frequency = $this->task->frequency;
$this->container = $this->task->container;
}
}
public function instantSave()
{
$this->validateOnly('task.enabled');
$this->task->save(['enabled' => $this->task->enabled]);
$this->dispatch('success', 'Scheduled task updated.');
$this->dispatch('refreshTasks');
try {
$this->syncData(true);
$this->dispatch('success', 'Scheduled task updated.');
$this->refreshTasks();
} catch (\Exception $e) {
return handleError($e);
}
}
public function submit()
{
$this->validate();
$this->task->name = str($this->task->name)->trim()->value();
$this->task->container = str($this->task->container)->trim()->value();
$this->task->save();
$this->dispatch('success', 'Scheduled task updated.');
$this->dispatch('refreshTasks');
try {
$this->syncData(true);
$this->dispatch('success', 'Scheduled task updated.');
} catch (\Exception $e) {
return handleError($e);
}
}
public function refreshTasks()
{
try {
$this->task->refresh();
} catch (\Exception $e) {
return handleError($e);
}
}
public function delete()
@@ -77,13 +127,23 @@ class Show extends Component
try {
$this->task->delete();
if ($this->type == 'application') {
return redirect()->route('project.application.configuration', $this->parameters, $this->scheduledTaskName);
if ($this->type === 'application') {
return redirect()->route('project.application.configuration', $this->parameters, $this->task->name);
} else {
return redirect()->route('project.service.configuration', $this->parameters, $this->scheduledTaskName);
return redirect()->route('project.service.configuration', $this->parameters, $this->task->name);
}
} catch (\Exception $e) {
return handleError($e);
}
}
public function executeNow()
{
try {
ScheduledTaskJob::dispatch($this->task);
$this->dispatch('success', 'Scheduled task executed.');
} catch (\Exception $e) {
return handleError($e);
}
}
}

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(
@@ -100,7 +100,6 @@ class Add extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submitFileStorageDirectory()
@@ -127,7 +126,6 @@ class Add extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submitPersistentVolume()
@@ -144,7 +142,6 @@ class Add extends Component
'mount_path' => $this->mount_path,
'host_path' => $this->host_path,
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Shared\Storages;
use App\Models\InstanceSettings;
use App\Models\LocalPersistentVolume;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
@@ -40,10 +41,12 @@ class Show extends Component
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
return;
}
}
$this->storage->delete();

View File

@@ -37,6 +37,7 @@ class Tags extends Component
$this->validate();
$tags = str($this->newTags)->trim()->explode(' ');
foreach ($tags as $tag) {
$tag = strip_tags($tag);
if (strlen($tag) < 2) {
$this->dispatch('error', 'Invalid tag.', "Tag <span class='dark:text-warning'>$tag</span> is invalid. Min length is 2.");
@@ -65,6 +66,7 @@ class Tags extends Component
public function addTag(string $id, string $name)
{
try {
$name = strip_tags($name);
if ($this->resource->tags()->where('id', $id)->exists()) {
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$name</span> already added.");

View File

@@ -26,7 +26,6 @@ class Terminal extends Component
#[On('send-terminal-command')]
public function sendTerminalCommand($isContainer, $identifier, $serverUuid)
{
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
if ($isContainer) {

View File

@@ -8,8 +8,11 @@ use Livewire\Component;
class UploadConfig extends Component
{
public $config;
public $applicationId;
public function mount() {
public function mount()
{
if (isDev()) {
$this->config = '{
"build_pack": "nixpacks",
@@ -22,6 +25,7 @@ class UploadConfig extends Component
}';
}
}
public function uploadConfig()
{
try {
@@ -30,10 +34,11 @@ class UploadConfig extends Component
$this->dispatch('success', 'Application settings updated');
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
return;
}
}
public function render()
{
return view('livewire.project.shared.upload-config');

View File

@@ -2,27 +2,46 @@
namespace App\Livewire\Project;
use App\Models\Environment;
use App\Models\Project;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Show extends Component
{
public Project $project;
public $environments;
#[Validate(['required', 'string', 'min:3'])]
public string $name;
public function mount()
#[Validate(['nullable', 'string'])]
public ?string $description = null;
public function mount(string $project_uuid)
{
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (! $project) {
return redirect()->route('dashboard');
try {
$this->project = Project::where('team_id', currentTeam()->id)->where('uuid', $project_uuid)->firstOrFail();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
$this->environments = $project->environments->sortBy('created_at');
$this->project = $project;
public function submit()
{
try {
$this->validate();
$environment = Environment::create([
'name' => $this->name,
'project_id' => $this->project->id,
]);
return redirect()->route('project.resource.index', [
'project_uuid' => $this->project->uuid,
'environment_name' => $environment->name,
]);
} catch (\Throwable $e) {
handleError($e, $this);
}
}
public function render()