Merge remote-tracking branch 'origin/next' into jean/allow-dots-username

This commit is contained in:
Andras Bacsai committed 2026-06-03 11:38:55 +02:00
commit 9aa40bb5f0
411 files changed
+21821 -8593

No files matched your search

+27
View File
@@ -2,11 +2,15 @@
namespace App\Livewire\Server;
use App\Actions\Server\StartSentinel;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Charts extends Component
{
use AuthorizesRequests;
public Server $server;
public $chartId = 'server';
@@ -28,6 +32,29 @@ class Charts extends Component
}
}
public function toggleMetrics(): void
{
try {
$this->authorize('update', $this->server);
$this->server->settings->is_metrics_enabled = ! $this->server->settings->is_metrics_enabled;
$this->server->settings->save();
$this->server->refresh();
if ($this->server->isMetricsEnabled()) {
StartSentinel::run($this->server, true);
$this->dispatch('success', 'Metrics enabled. Starting Sentinel.');
$this->dispatch('refreshServerShow');
$this->redirect(route('server.metrics', ['server_uuid' => $this->server->uuid]), navigate: true);
} else {
$this->server->restartSentinel();
$this->dispatch('success', 'Metrics disabled. Restarting Sentinel.');
$this->dispatch('refreshServerShow');
}
} catch (\Throwable $e) {
handleError($e, $this);
}
}
public function pollData()
{
if ($this->poll || $this->interval <= 10) {
+1 -1
View File
@@ -45,7 +45,7 @@ class Destinations extends Component
} else {
SwarmDocker::create([
'name' => $this->server->name.'-'.$name,
'network' => $this->name,
'network' => $name,
'server_id' => $this->server->id,
]);
}
@@ -28,12 +28,11 @@ class DynamicConfigurationNavbar extends Component
// Decode filename: pipes are used to encode dots for Livewire property binding
// (e.g., 'my|service.yaml' -> 'my.service.yaml')
// This must happen BEFORE validation because validateShellSafePath() correctly
// rejects pipe characters as dangerous shell metacharacters
// This must happen BEFORE validation because validateFilenameSafe()
// rejects pipe characters through validateShellSafePath().
$file = str_replace('|', '.', $fileName);
// Validate filename to prevent command injection
validateShellSafePath($file, 'proxy configuration filename');
validateFilenameSafe($file, 'proxy configuration filename');
if ($proxy_type === 'CADDY' && $file === 'Caddyfile') {
$this->dispatch('error', 'Cannot delete Caddyfile.');
@@ -43,8 +43,7 @@ class NewDynamicConfiguration extends Component
'value' => 'required',
]);
// Additional security validation to prevent command injection
validateShellSafePath($this->fileName, 'proxy configuration filename');
validateFilenameSafe($this->fileName, 'proxy configuration filename');
if (data_get($this->parameters, 'server_uuid')) {
$this->server = Server::ownedByCurrentTeam()->whereUuid(data_get($this->parameters, 'server_uuid'))->first();
+11 -15
View File
@@ -15,8 +15,6 @@ class Sentinel extends Component
public Server $server;
public array $parameters = [];
public bool $isMetricsEnabled;
#[Validate(['required', 'string', 'max:500', 'regex:/\A[a-zA-Z0-9._\-+=\/]+\z/'])]
@@ -51,15 +49,9 @@ class Sentinel extends Component
];
}
public function mount(string $server_uuid)
public function mount()
{
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
$this->parameters = get_route_parameters();
$this->syncData();
} catch (\Throwable) {
return redirect()->route('server.index');
}
$this->syncData();
}
public function syncData(bool $toModel = false)
@@ -93,7 +85,9 @@ class Sentinel extends Component
{
if ($event['serverUuid'] === $this->server->uuid) {
$this->server->refresh();
$this->syncData();
// Only refresh display-only state; never re-sync text-input properties
// (would clobber any unsaved typing — see coolify#6062 / #6354 / #9695).
$this->sentinelUpdatedAt = $this->server->sentinel_updated_at;
$this->dispatch('success', 'Sentinel has been restarted successfully.');
}
}
@@ -110,27 +104,29 @@ class Sentinel extends Component
}
}
public function updatedIsSentinelEnabled($value)
public function toggleSentinel(): void
{
try {
$this->authorize('manageSentinel', $this->server);
if ($value === true) {
if (! $this->isSentinelEnabled) {
if ($this->server->isBuildServer()) {
$this->isSentinelEnabled = false;
$this->dispatch('error', 'Sentinel cannot be enabled on build servers.');
return;
}
$this->isSentinelEnabled = true;
$customImage = isDev() ? $this->sentinelCustomDockerImage : null;
StartSentinel::run($this->server, true, null, $customImage);
} else {
$this->isSentinelEnabled = false;
$this->isMetricsEnabled = false;
$this->isSentinelDebugEnabled = false;
StopSentinel::dispatch($this->server);
}
$this->submit();
$this->dispatch('refreshServerShow');
} catch (\Throwable $e) {
return handleError($e, $this);
handleError($e, $this);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Livewire\Server\Sentinel;
use App\Models\Server;
use Illuminate\View\View;
use Livewire\Component;
class Logs extends Component
{
public ?Server $server = null;
public array $parameters = [];
public function mount(): void
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail();
} catch (\Throwable $e) {
handleError($e, $this);
}
}
public function render(): View
{
return view('livewire.server.sentinel.logs');
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Livewire\Server\Sentinel;
use App\Models\Server;
use Illuminate\View\View;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public array $parameters = [];
public function mount(): void
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail();
} catch (\Throwable $e) {
handleError($e, $this);
}
}
public function render(): View
{
return view('livewire.server.sentinel.show');
}
}
+10 -5
View File
@@ -278,7 +278,9 @@ class Show extends Component
// Only refresh if the event is for this server
if (isset($event['serverUuid']) && $event['serverUuid'] === $this->server->uuid) {
$this->server->refresh();
$this->syncData();
// Only refresh display-only state; never re-sync text-input properties
// (would clobber any unsaved typing — see coolify#6062 / #6354 / #9695).
$this->sentinelUpdatedAt = $this->server->sentinel_updated_at;
$this->dispatch('success', 'Sentinel has been restarted successfully.');
}
}
@@ -458,12 +460,15 @@ class Show extends Component
return;
}
// Refresh server data
// Refresh server data and only the display-only state that validation produces.
// Never re-sync text-input properties via syncData() — would clobber any
// unsaved typing (see coolify#6062 / #6354 / #9695).
$this->server->refresh();
$this->syncData();
// Update validation state
$this->server->settings->refresh();
$this->isValidating = $this->server->is_validating ?? false;
$this->validationLogs = $this->server->validation_logs;
$this->isReachable = $this->server->settings->is_reachable;
$this->isUsable = $this->server->settings->is_usable;
// Reload Hetzner tokens in case the linking section should now be shown
$this->loadHetznerTokens();