mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-28 09:34:36 +08:00
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Furox <177975472+Furox-Art@users.noreply.github.com> Co-authored-by: johandenhollander <johan.den.hollander@finalist.nl> Co-authored-by: João Cruz <161638790+SrJooJ@users.noreply.github.com> Co-authored-by: Saim Hashmi <hashmisaim037@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
278 lines
9.2 KiB
PHP
278 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Settings;
|
|
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\Server;
|
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
use Livewire\Attributes\Computed;
|
|
use Livewire\Attributes\Validate;
|
|
use Livewire\Component;
|
|
|
|
class Index extends Component
|
|
{
|
|
use AuthorizesRequests;
|
|
|
|
public InstanceSettings $settings;
|
|
|
|
public ?Server $server = null;
|
|
|
|
#[Validate('nullable|string|max:255|url')]
|
|
public ?string $fqdn = null;
|
|
|
|
#[Validate('boolean')]
|
|
public bool $is_dashboard_force_https_enabled = true;
|
|
|
|
#[Validate('required|integer|min:1025|max:65535')]
|
|
public int $public_port_min;
|
|
|
|
#[Validate('required|integer|min:1025|max:65535')]
|
|
public int $public_port_max;
|
|
|
|
#[Validate('nullable|string|max:255')]
|
|
public ?string $instance_name = null;
|
|
|
|
#[Validate('nullable|ipv4')]
|
|
public ?string $public_ipv4 = null;
|
|
|
|
#[Validate('nullable|ipv6')]
|
|
public ?string $public_ipv6 = null;
|
|
|
|
#[Validate('required|string|timezone')]
|
|
public string $instance_timezone;
|
|
|
|
#[Validate(['nullable', 'string', 'max:128', 'regex:/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/'])]
|
|
public ?string $dev_helper_version = null;
|
|
|
|
public array $domainConflicts = [];
|
|
|
|
public bool $showDomainConflictModal = false;
|
|
|
|
public bool $forceSaveDomains = false;
|
|
|
|
protected array $messages = [
|
|
'fqdn.url' => 'Invalid instance URL.',
|
|
'fqdn.max' => 'URL must not exceed 255 characters.',
|
|
'dev_helper_version.regex' => 'Dev helper version must match Docker tag format (alphanumeric, _, ., -; first char cannot be . or -).',
|
|
];
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.settings.index');
|
|
}
|
|
|
|
public function mount()
|
|
{
|
|
if (! isInstanceAdmin()) {
|
|
return redirect()->route('dashboard');
|
|
}
|
|
$this->settings = instanceSettings();
|
|
if (! isCloud()) {
|
|
$this->server = Server::findOrFail(0);
|
|
}
|
|
$this->fqdn = $this->settings->fqdn;
|
|
$this->is_dashboard_force_https_enabled = $this->settings->is_dashboard_force_https_enabled;
|
|
$this->public_port_min = $this->settings->public_port_min;
|
|
$this->public_port_max = $this->settings->public_port_max;
|
|
$this->instance_name = $this->settings->instance_name;
|
|
$this->public_ipv4 = $this->settings->public_ipv4;
|
|
$this->public_ipv6 = $this->settings->public_ipv6;
|
|
$this->instance_timezone = $this->settings->instance_timezone;
|
|
$this->dev_helper_version = $this->settings->dev_helper_version;
|
|
}
|
|
|
|
#[Computed]
|
|
public function timezones(): array
|
|
{
|
|
return collect(timezone_identifiers_list())
|
|
->sort()
|
|
->values()
|
|
->toArray();
|
|
}
|
|
|
|
public function instantSave($isSave = true)
|
|
{
|
|
$this->authorize('update', $this->settings);
|
|
$this->validate();
|
|
$this->settings->fqdn = $this->fqdn ? trim($this->fqdn) : $this->fqdn;
|
|
$this->settings->is_dashboard_force_https_enabled = $this->is_dashboard_force_https_enabled;
|
|
$this->settings->public_port_min = $this->public_port_min;
|
|
$this->settings->public_port_max = $this->public_port_max;
|
|
$this->settings->instance_name = $this->instance_name;
|
|
$this->settings->public_ipv4 = $this->public_ipv4;
|
|
$this->settings->public_ipv6 = $this->public_ipv6;
|
|
$this->settings->instance_timezone = $this->instance_timezone;
|
|
$this->settings->dev_helper_version = $this->dev_helper_version;
|
|
if ($isSave) {
|
|
$this->saveAndAudit();
|
|
$this->dispatch('success', 'Settings updated!');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the instance settings and record the changed field names.
|
|
*/
|
|
private function saveAndAudit(): void
|
|
{
|
|
$changedFields = auditChangedFields($this->settings);
|
|
$this->settings->save();
|
|
|
|
if ($changedFields === []) {
|
|
return;
|
|
}
|
|
|
|
auditLog('ui.instance.settings.updated', [
|
|
'team_id' => null,
|
|
'resource' => 'instance',
|
|
'section' => 'general',
|
|
'changed_fields' => $changedFields,
|
|
]);
|
|
}
|
|
|
|
public function confirmDomainUsage()
|
|
{
|
|
$this->authorize('update', $this->settings);
|
|
$this->forceSaveDomains = true;
|
|
$this->showDomainConflictModal = false;
|
|
$this->submit();
|
|
}
|
|
|
|
public function submit()
|
|
{
|
|
try {
|
|
$this->authorize('update', $this->settings);
|
|
$error_show = false;
|
|
$this->resetErrorBag();
|
|
|
|
if (! validate_timezone($this->instance_timezone)) {
|
|
$this->instance_timezone = config('app.timezone');
|
|
throw new \Exception('Invalid timezone.');
|
|
} else {
|
|
$this->settings->instance_timezone = $this->instance_timezone;
|
|
}
|
|
|
|
if ($this->settings->public_port_min > $this->settings->public_port_max) {
|
|
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
|
|
|
|
return;
|
|
}
|
|
|
|
// Trim FQDN to remove leading/trailing whitespace before validation
|
|
if ($this->fqdn) {
|
|
$this->fqdn = trim($this->fqdn);
|
|
}
|
|
|
|
$this->validate();
|
|
|
|
if ($this->settings->is_dns_validation_enabled && $this->fqdn && $this->server) {
|
|
if (! validateDNSEntry($this->fqdn, $this->server)) {
|
|
$target = serverDnsTargetIp($this->server) ?? $this->server->ip;
|
|
$guidance = dnsMismatchGuidanceMessage($target, $target);
|
|
$this->dispatch('error', "Validating DNS failed.<br><br>{$guidance}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
|
$error_show = true;
|
|
}
|
|
}
|
|
if ($this->fqdn) {
|
|
if (! $this->forceSaveDomains) {
|
|
$result = checkDomainUsage(domain: $this->fqdn);
|
|
if ($result['hasConflicts']) {
|
|
$this->domainConflicts = $result['conflicts'];
|
|
$this->showDomainConflictModal = true;
|
|
|
|
return;
|
|
}
|
|
} else {
|
|
// Reset the force flag after using it
|
|
$this->forceSaveDomains = false;
|
|
}
|
|
}
|
|
|
|
$this->instantSave(isSave: false);
|
|
|
|
$this->saveAndAudit();
|
|
if ($this->server) {
|
|
$this->server->setupDynamicProxyConfiguration();
|
|
}
|
|
if (! $error_show) {
|
|
$this->dispatch('success', 'Instance settings updated successfully!');
|
|
}
|
|
} catch (\Exception $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Look up the public IPs of this host again and save them.
|
|
*/
|
|
public function detectPublicIps()
|
|
{
|
|
try {
|
|
$this->authorize('update', $this->settings);
|
|
|
|
$detected = detect_public_ips();
|
|
if ($detected === []) {
|
|
$this->dispatch('error', 'Could not detect a public IP address.');
|
|
|
|
return;
|
|
}
|
|
|
|
$this->settings->fill($detected);
|
|
$this->saveAndAudit();
|
|
$this->public_ipv4 = $this->settings->public_ipv4;
|
|
$this->public_ipv6 = $this->settings->public_ipv6;
|
|
$this->dispatch('success', 'Detected '.implode(' and ', $detected).'.');
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function buildHelperImage()
|
|
{
|
|
try {
|
|
$this->authorize('update', $this->settings);
|
|
if (! isDev()) {
|
|
$this->dispatch('error', 'Building helper image is only available in development mode.');
|
|
|
|
return;
|
|
}
|
|
|
|
if (! $this->server) {
|
|
$this->dispatch('error', 'Server not available.');
|
|
|
|
return;
|
|
}
|
|
|
|
$this->validateOnly('dev_helper_version');
|
|
|
|
$version = $this->dev_helper_version ?: config('constants.coolify.helper_version');
|
|
if (empty($version)) {
|
|
$this->dispatch('error', 'Please specify a version to build.');
|
|
|
|
return;
|
|
}
|
|
|
|
if (! preg_match('/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/', (string) $version)) {
|
|
$this->dispatch('error', 'Invalid helper version format.');
|
|
|
|
return;
|
|
}
|
|
|
|
$imageRef = escapeshellarg(coolifyHelperImage().":{$version}");
|
|
$buildCommand = "docker build -t {$imageRef} -f docker/coolify-helper/Dockerfile .";
|
|
|
|
$activity = remote_process(
|
|
command: [$buildCommand],
|
|
server: $this->server,
|
|
type: 'build-helper-image'
|
|
);
|
|
|
|
$this->buildActivityId = $activity->id;
|
|
$this->dispatch('activityMonitor', $activity->id);
|
|
|
|
$this->dispatch('success', "Building coolify-helper:{$version}...");
|
|
} catch (\Exception $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
}
|