Merge remote-tracking branch 'origin/next' into audit-policies

# Conflicts:
#	app/Http/Controllers/Api/SecurityController.php
#	app/Http/Controllers/Api/ServersController.php
#	app/Livewire/Admin/Index.php
#	app/Livewire/Destination/Show.php
#	app/Livewire/NavbarDeleteTeam.php
#	app/Livewire/Project/Application/Previews.php
#	app/Livewire/Project/DeleteProject.php
#	app/Livewire/Project/Shared/ResourceOperations.php
#	app/Livewire/Server/Resources.php
#	app/Livewire/Server/ValidateAndInstall.php
#	app/Livewire/Storage/Show.php
#	resources/views/livewire/dashboard.blade.php
#	resources/views/livewire/project/application/heading.blade.php
#	resources/views/livewire/project/database/heading.blade.php
#	resources/views/livewire/project/service/heading.blade.php
#	resources/views/livewire/project/shared/environment-variable/show.blade.php
#	resources/views/livewire/project/shared/scheduled-task/show.blade.php
#	tests/Feature/Security/TrustHostsMiddlewareTest.php
This commit is contained in:
Andras Bacsai committed 2026-04-19 15:19:37 +02:00
commit 43f33d94ad
685 files changed
+41904 -5674

No files matched your search

+12 -7
View File
@@ -45,10 +45,10 @@ class Danger extends Component
if ($this->resource === null) {
if (isset($parameters['service_uuid'])) {
$this->resource = Service::where('uuid', $parameters['service_uuid'])->first();
$this->resource = Service::ownedByCurrentTeam()->where('uuid', $parameters['service_uuid'])->first();
} elseif (isset($parameters['stack_service_uuid'])) {
$this->resource = ServiceApplication::where('uuid', $parameters['stack_service_uuid'])->first()
?? ServiceDatabase::where('uuid', $parameters['stack_service_uuid'])->first();
$this->resource = ServiceApplication::ownedByCurrentTeam()->where('uuid', $parameters['stack_service_uuid'])->first()
?? ServiceDatabase::ownedByCurrentTeam()->where('uuid', $parameters['stack_service_uuid'])->first();
}
}
@@ -88,16 +88,21 @@ class Danger extends Component
}
}
public function delete($password)
public function delete($password, $selectedActions = [])
{
if (! verifyPasswordConfirmation($password, $this)) {
return;
return 'The provided password is incorrect.';
}
if (! $this->resource) {
$this->addError('resource', 'Resource not found.');
return 'Resource not found.';
}
return;
if (! empty($selectedActions)) {
$this->delete_volumes = in_array('delete_volumes', $selectedActions);
$this->delete_connected_networks = in_array('delete_connected_networks', $selectedActions);
$this->delete_configurations = in_array('delete_configurations', $selectedActions);
$this->docker_cleanup = in_array('docker_cleanup', $selectedActions);
}
try {
+4 -2
View File
@@ -149,12 +149,12 @@ class Destination extends Component
}
}
public function removeServer(int $network_id, int $server_id, $password)
public function removeServer(int $network_id, int $server_id, $password, $selectedActions = [])
{
try {
$this->authorize('update', $this->resource);
if (! verifyPasswordConfirmation($password, $this)) {
return;
return 'The provided password is incorrect.';
}
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {
@@ -168,6 +168,8 @@ class Destination extends Component
$this->loadData();
$this->dispatch('refresh');
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
return true;
} catch (\Exception $e) {
return handleError($e, $this);
}
@@ -71,6 +71,7 @@ class Add extends Component
'team' => [],
'project' => [],
'environment' => [],
'server' => [],
];
// Early return if no team
@@ -126,6 +127,66 @@ class Add extends Component
}
}
// Get server variables
$serverUuid = data_get($this->parameters, 'server_uuid');
if ($serverUuid) {
// If we have a specific server_uuid, show variables for that server
$server = \App\Models\Server::where('team_id', $team->id)
->where('uuid', $serverUuid)
->first();
if ($server) {
try {
$this->authorize('view', $server);
$result['server'] = $server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
} else {
// For application environment variables, try to use the application's destination server
$applicationUuid = data_get($this->parameters, 'application_uuid');
if ($applicationUuid) {
$application = \App\Models\Application::whereRelation('environment.project.team', 'id', $team->id)
->where('uuid', $applicationUuid)
->with('destination.server')
->first();
if ($application && $application->destination && $application->destination->server) {
try {
$this->authorize('view', $application->destination->server);
$result['server'] = $application->destination->server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
} else {
// For service environment variables, try to use the service's server
$serviceUuid = data_get($this->parameters, 'service_uuid');
if ($serviceUuid) {
$service = \App\Models\Service::whereRelation('environment.project.team', 'id', $team->id)
->where('uuid', $serviceUuid)
->with('server')
->first();
if ($service && $service->server) {
try {
$this->authorize('view', $service->server);
$result['server'] = $service->server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
}
}
}
return $result;
}
@@ -100,6 +100,9 @@ class Show extends Component
public function refresh()
{
if (! $this->env->exists || ! $this->env->fresh()) {
return;
}
$this->syncData();
$this->checkEnvs();
}
@@ -225,6 +228,7 @@ class Show extends Component
'team' => [],
'project' => [],
'environment' => [],
'server' => [],
];
// Early return if no team
@@ -280,6 +284,66 @@ class Show extends Component
}
}
// Get server variables
$serverUuid = data_get($this->parameters, 'server_uuid');
if ($serverUuid) {
// If we have a specific server_uuid, show variables for that server
$server = \App\Models\Server::where('team_id', $team->id)
->where('uuid', $serverUuid)
->first();
if ($server) {
try {
$this->authorize('view', $server);
$result['server'] = $server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
} else {
// For application environment variables, try to use the application's destination server
$applicationUuid = data_get($this->parameters, 'application_uuid');
if ($applicationUuid) {
$application = \App\Models\Application::whereRelation('environment.project.team', 'id', $team->id)
->where('uuid', $applicationUuid)
->with('destination.server')
->first();
if ($application && $application->destination && $application->destination->server) {
try {
$this->authorize('view', $application->destination->server);
$result['server'] = $application->destination->server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
} else {
// For service environment variables, try to use the service's server
$serviceUuid = data_get($this->parameters, 'service_uuid');
if ($serviceUuid) {
$service = \App\Models\Service::whereRelation('environment.project.team', 'id', $team->id)
->where('uuid', $serviceUuid)
->with('server')
->first();
if ($service && $service->server) {
try {
$this->authorize('view', $service->server);
$result['server'] = $service->server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
}
}
}
return $result;
}
@@ -5,6 +5,7 @@ namespace App\Livewire\Project\Shared;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Support\ValidationPatterns;
use Illuminate\Support\Collection;
use Livewire\Attributes\On;
use Livewire\Component;
@@ -38,7 +39,7 @@ class ExecuteContainerCommand extends Component
$this->servers = collect();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->resource = Application::ownedByCurrentTeam()->where('uuid', $this->parameters['application_uuid'])->firstOrFail();
if ($this->resource->destination->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->destination->server);
}
@@ -61,14 +62,14 @@ class ExecuteContainerCommand extends Component
$this->loadContainers();
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->resource = Service::ownedByCurrentTeam()->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->resource = Server::ownedByCurrentTeam()->where('uuid', $this->parameters['server_uuid'])->firstOrFail();
$this->servers = $this->servers->push($this->resource);
}
$this->servers = $this->servers->sortByDesc(fn ($server) => $server->isTerminalEnabled());
@@ -181,7 +182,7 @@ class ExecuteContainerCommand extends Component
}
try {
// Validate container name format
if (! preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $this->selected_container)) {
if (! ValidationPatterns::isValidContainerName($this->selected_container)) {
throw new \InvalidArgumentException('Invalid container name format');
}
+27 -5
View File
@@ -16,7 +16,9 @@ use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Support\ValidationPatterns;
use Illuminate\Support\Facades\Process;
use Livewire\Attributes\Locked;
use Livewire\Component;
class GetLogs extends Component
@@ -29,12 +31,16 @@ class GetLogs extends Component
public string $errors = '';
#[Locked]
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|null $resource = null;
#[Locked]
public ServiceApplication|ServiceDatabase|null $servicesubtype = null;
#[Locked]
public Server $server;
#[Locked]
public ?string $container = null;
public ?string $displayName = null;
@@ -54,7 +60,7 @@ class GetLogs extends Component
public function mount()
{
if (! is_null($this->resource)) {
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
if ($this->resource->getMorphClass() === Application::class) {
$this->showTimeStamps = $this->resource->settings->is_include_timestamps;
} else {
if ($this->servicesubtype) {
@@ -63,7 +69,7 @@ class GetLogs extends Component
$this->showTimeStamps = $this->resource->is_include_timestamps;
}
}
if ($this->resource?->getMorphClass() === \App\Models\Application::class) {
if ($this->resource?->getMorphClass() === Application::class) {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = 'Pull Request: '.str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
}
@@ -74,11 +80,11 @@ class GetLogs extends Component
public function instantSave()
{
if (! is_null($this->resource)) {
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
if ($this->resource->getMorphClass() === Application::class) {
$this->resource->settings->is_include_timestamps = $this->showTimeStamps;
$this->resource->settings->save();
}
if ($this->resource->getMorphClass() === \App\Models\Service::class) {
if ($this->resource->getMorphClass() === Service::class) {
$serviceName = str($this->container)->beforeLast('-')->value();
$subType = $this->resource->applications()->where('name', $serviceName)->first();
if ($subType) {
@@ -118,10 +124,20 @@ class GetLogs extends Component
public function getLogs($refresh = false)
{
if (! Server::ownedByCurrentTeam()->where('id', $this->server->id)->exists()) {
$this->outputs = 'Unauthorized.';
return;
}
if (! $this->server->isFunctional()) {
return;
}
if (! $refresh && ! $this->expandByDefault && ($this->resource?->getMorphClass() === \App\Models\Service::class || str($this->container)->contains('-pr-'))) {
if ($this->container && ! ValidationPatterns::isValidContainerName($this->container)) {
$this->outputs = 'Invalid container name.';
return;
}
if (! $refresh && ! $this->expandByDefault && ($this->resource?->getMorphClass() === Service::class || str($this->container)->contains('-pr-'))) {
return;
}
if ($this->numberOfLines <= 0 || is_null($this->numberOfLines)) {
@@ -194,9 +210,15 @@ class GetLogs extends Component
public function downloadAllLogs(): string
{
if (! Server::ownedByCurrentTeam()->where('id', $this->server->id)->exists()) {
return '';
}
if (! $this->server->isFunctional() || ! $this->container) {
return '';
}
if (! ValidationPatterns::isValidContainerName($this->container)) {
return '';
}
if ($this->showTimeStamps) {
if ($this->server->isSwarm()) {
+2 -2
View File
@@ -34,7 +34,7 @@ class HealthChecks extends Component
#[Validate(['nullable', 'integer', 'min:1', 'max:65535'])]
public ?string $healthCheckPort = null;
#[Validate(['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%]+$#'])]
#[Validate(['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%,;]+$#'])]
public string $healthCheckPath;
#[Validate(['integer'])]
@@ -62,7 +62,7 @@ class HealthChecks extends Component
'healthCheckEnabled' => 'boolean',
'healthCheckType' => 'string|in:http,cmd',
'healthCheckCommand' => ['nullable', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'],
'healthCheckPath' => ['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%]+$#'],
'healthCheckPath' => ['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%,;]+$#'],
'healthCheckPort' => 'nullable|integer|min:1|max:65535',
'healthCheckHost' => ['required', 'string', 'regex:/^[a-zA-Z0-9.\-_]+$/'],
'healthCheckMethod' => 'required|string|in:GET,HEAD,POST,OPTIONS',
+2 -2
View File
@@ -106,7 +106,7 @@ class Logs extends Component
$this->query = request()->query();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->resource = Application::ownedByCurrentTeam()->where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->status = $this->resource->status;
if ($this->resource->destination->server->isFunctional()) {
$server = $this->resource->destination->server;
@@ -133,7 +133,7 @@ class Logs extends Component
$this->containers->push($this->container);
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->resource = Service::ownedByCurrentTeam()->where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->resource->applications()->get()->each(function ($application) {
$this->containers->push(data_get($application, 'name').'-'.data_get($this->resource, 'uuid'));
});
+32 -12
View File
@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Shared;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Validation\ValidationException;
use Livewire\Component;
class ResourceLimits extends Component
@@ -16,24 +17,24 @@ class ResourceLimits extends Component
public ?string $limitsCpuset = null;
public ?int $limitsCpuShares = null;
public mixed $limitsCpuShares = null;
public string $limitsMemory;
public string $limitsMemorySwap;
public int $limitsMemorySwappiness;
public mixed $limitsMemorySwappiness = 0;
public string $limitsMemoryReservation;
protected $rules = [
'limitsMemory' => 'required|string',
'limitsMemorySwap' => 'required|string',
'limitsMemory' => ['required', 'string', 'regex:/^(0|\d+[bBkKmMgG])$/'],
'limitsMemorySwap' => ['required', 'string', 'regex:/^(0|\d+[bBkKmMgG])$/'],
'limitsMemorySwappiness' => 'required|integer|min:0|max:100',
'limitsMemoryReservation' => 'required|string',
'limitsCpus' => 'nullable',
'limitsCpuset' => 'nullable',
'limitsCpuShares' => 'nullable',
'limitsMemoryReservation' => ['required', 'string', 'regex:/^(0|\d+[bBkKmMgG])$/'],
'limitsCpus' => ['nullable', 'regex:/^\d*\.?\d+$/'],
'limitsCpuset' => ['nullable', 'regex:/^\d+([,-]\d+)*$/'],
'limitsCpuShares' => 'nullable|integer|min:0',
];
protected $validationAttributes = [
@@ -46,6 +47,19 @@ class ResourceLimits extends Component
'limitsCpuShares' => 'cpu shares',
];
protected $messages = [
'limitsMemory.regex' => 'Maximum Memory Limit must be a number followed by a unit (b, k, m, g). Example: 256m, 1g. Use 0 for unlimited.',
'limitsMemorySwap.regex' => 'Maximum Swap Limit must be a number followed by a unit (b, k, m, g). Example: 256m, 1g. Use 0 for unlimited.',
'limitsMemoryReservation.regex' => 'Soft Memory Limit must be a number followed by a unit (b, k, m, g). Example: 256m, 1g. Use 0 for unlimited.',
'limitsCpus.regex' => 'Number of CPUs must be a number (integer or decimal). Example: 0.5, 2.',
'limitsCpuset.regex' => 'CPU sets must be a comma-separated list of CPU numbers or ranges. Example: 0-2 or 0,1,3.',
'limitsMemorySwappiness.integer' => 'Swappiness must be a whole number between 0 and 100.',
'limitsMemorySwappiness.min' => 'Swappiness must be between 0 and 100.',
'limitsMemorySwappiness.max' => 'Swappiness must be between 0 and 100.',
'limitsCpuShares.integer' => 'CPU Weight must be a whole number.',
'limitsCpuShares.min' => 'CPU Weight must be a positive number.',
];
/**
* Sync data between component properties and model
*
@@ -57,10 +71,10 @@ class ResourceLimits extends Component
// Sync TO model (before save)
$this->resource->limits_cpus = $this->limitsCpus;
$this->resource->limits_cpuset = $this->limitsCpuset;
$this->resource->limits_cpu_shares = $this->limitsCpuShares;
$this->resource->limits_cpu_shares = (int) $this->limitsCpuShares;
$this->resource->limits_memory = $this->limitsMemory;
$this->resource->limits_memory_swap = $this->limitsMemorySwap;
$this->resource->limits_memory_swappiness = $this->limitsMemorySwappiness;
$this->resource->limits_memory_swappiness = (int) $this->limitsMemorySwappiness;
$this->resource->limits_memory_reservation = $this->limitsMemoryReservation;
} else {
// Sync FROM model (on load/refresh)
@@ -91,7 +105,7 @@ class ResourceLimits extends Component
if (! $this->limitsMemorySwap) {
$this->limitsMemorySwap = '0';
}
if (is_null($this->limitsMemorySwappiness)) {
if ($this->limitsMemorySwappiness === '' || is_null($this->limitsMemorySwappiness)) {
$this->limitsMemorySwappiness = 60;
}
if (! $this->limitsMemoryReservation) {
@@ -103,7 +117,7 @@ class ResourceLimits extends Component
if ($this->limitsCpuset === '') {
$this->limitsCpuset = null;
}
if (is_null($this->limitsCpuShares)) {
if ($this->limitsCpuShares === '' || is_null($this->limitsCpuShares)) {
$this->limitsCpuShares = 1024;
}
@@ -112,6 +126,12 @@ class ResourceLimits extends Component
$this->syncData(true);
$this->resource->save();
$this->dispatch('success', 'Resource limits updated.');
} catch (ValidationException $e) {
foreach ($e->validator->errors()->all() as $message) {
$this->dispatch('error', $message);
}
return;
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -7,9 +7,18 @@ use App\Actions\Database\StopDatabase;
use App\Actions\Service\StartService;
use App\Actions\Service\StopService;
use App\Jobs\VolumeCloneJob;
use App\Models\Application;
use App\Models\Environment;
use App\Models\Project;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDocker;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Models\SwarmDocker;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
@@ -50,10 +59,9 @@ class ResourceOperations extends Component
try {
$this->authorize('update', $this->resource);
$teamScope = fn ($q) => $q->where('team_id', currentTeam()->id);
$new_destination = StandaloneDocker::whereHas('server', $teamScope)->find($destination_id);
$new_destination = StandaloneDocker::ownedByCurrentTeam()->find($destination_id);
if (! $new_destination) {
$new_destination = SwarmDocker::whereHas('server', $teamScope)->find($destination_id);
$new_destination = SwarmDocker::ownedByCurrentTeam()->find($destination_id);
}
if (! $new_destination) {
return $this->addError('destination_id', 'Destination not found.');
@@ -61,7 +69,7 @@ class ResourceOperations extends Component
$uuid = (string) new Cuid2;
$server = $new_destination->server;
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
if ($this->resource->getMorphClass() === Application::class) {
$new_resource = clone_application($this->resource, $new_destination, ['uuid' => $uuid], $this->cloneVolumeData);
$route = route('project.application.configuration', [
@@ -72,14 +80,14 @@ class ResourceOperations extends Component
return redirect()->to($route);
} elseif (
$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
$this->resource->getMorphClass() === StandalonePostgresql::class ||
$this->resource->getMorphClass() === StandaloneMongodb::class ||
$this->resource->getMorphClass() === StandaloneMysql::class ||
$this->resource->getMorphClass() === StandaloneMariadb::class ||
$this->resource->getMorphClass() === StandaloneRedis::class ||
$this->resource->getMorphClass() === StandaloneKeydb::class ||
$this->resource->getMorphClass() === StandaloneDragonfly::class ||
$this->resource->getMorphClass() === StandaloneClickhouse::class
) {
$uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate([
@@ -134,6 +142,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
'uuid',
])->fill([
'name' => $newName,
'resource_id' => $new_resource->id,
@@ -217,7 +226,7 @@ class ResourceOperations extends Component
'name' => $this->resource->name.'-clone-'.$uuid,
'destination_id' => $new_destination->id,
'destination_type' => $new_destination->getMorphClass(),
'server_id' => $new_destination->server_id, // server_id is probably not needed anymore because of the new polymorphic relationships (here it is needed for clone to a different server to work - but maybe we can drop the column)
'server_id' => $new_destination->server_id,
]);
$new_resource->save();
@@ -255,9 +264,9 @@ class ResourceOperations extends Component
}
foreach ($new_resource->applications() as $application) {
$application->update([
$application->fill([
'status' => 'exited',
]);
])->save();
$persistentVolumes = $application->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
@@ -272,6 +281,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
'uuid',
])->fill([
'name' => $newName,
'resource_id' => $application->id,
@@ -297,9 +307,9 @@ class ResourceOperations extends Component
}
foreach ($new_resource->databases() as $database) {
$database->update([
$database->fill([
'status' => 'exited',
]);
])->save();
$persistentVolumes = $database->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
@@ -314,6 +324,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
'uuid',
])->fill([
'name' => $newName,
'resource_id' => $database->id,
@@ -358,9 +369,9 @@ class ResourceOperations extends Component
try {
$this->authorize('update', $this->resource);
$new_environment = Environment::ownedByCurrentTeam()->findOrFail($environment_id);
$this->resource->update([
$this->resource->fill([
'environment_id' => $environment_id,
]);
])->save();
if ($this->resource->type() === 'application') {
$route = route('project.application.configuration', [
'project_uuid' => $new_environment->project->uuid,
@@ -52,18 +52,15 @@ class Show extends Component
#[Locked]
public string $task_uuid;
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ServiceChecked" => '$refresh',
];
}
public function mount(string $task_uuid, string $project_uuid, string $environment_uuid, ?string $application_uuid = null, ?string $service_uuid = null)
public function mount()
{
try {
$task_uuid = request()->route('task_uuid');
$project_uuid = request()->route('project_uuid');
$environment_uuid = request()->route('environment_uuid');
$application_uuid = request()->route('application_uuid');
$service_uuid = request()->route('service_uuid');
$this->task_uuid = $task_uuid;
if ($application_uuid) {
$this->type = 'application';
@@ -114,6 +111,19 @@ class Show extends Component
}
}
public function toggleEnabled()
{
try {
$this->authorize('update', $this->resource);
$this->isEnabled = ! $this->isEnabled;
$this->task->enabled = $this->isEnabled;
$this->task->save();
$this->dispatch('success', $this->isEnabled ? 'Scheduled task enabled.' : 'Scheduled task disabled.');
} catch (\Exception $e) {
return handleError($e);
}
}
public function instantSave()
{
try {
+19 -2
View File
@@ -29,10 +29,13 @@ class Show extends Component
public ?string $hostPath = null;
public bool $isPreviewSuffixEnabled = true;
protected $rules = [
'name' => 'required|string',
'mountPath' => 'required|string',
'hostPath' => 'string|nullable',
'isPreviewSuffixEnabled' => 'required|boolean',
];
protected $validationAttributes = [
@@ -53,11 +56,13 @@ class Show extends Component
$this->storage->name = $this->name;
$this->storage->mount_path = $this->mountPath;
$this->storage->host_path = $this->hostPath;
$this->storage->is_preview_suffix_enabled = $this->isPreviewSuffixEnabled;
} else {
// Sync FROM model (on load/refresh)
$this->name = $this->storage->name;
$this->mountPath = $this->storage->mount_path;
$this->hostPath = $this->storage->host_path;
$this->isPreviewSuffixEnabled = $this->storage->is_preview_suffix_enabled ?? true;
}
}
@@ -67,6 +72,16 @@ class Show extends Component
$this->isReadOnly = $this->storage->shouldBeReadOnlyInUI();
}
public function instantSave(): void
{
$this->authorize('update', $this->resource);
$this->validate();
$this->syncData(true);
$this->storage->save();
$this->dispatch('success', 'Storage updated successfully');
}
public function submit()
{
$this->authorize('update', $this->resource);
@@ -77,15 +92,17 @@ class Show extends Component
$this->dispatch('success', 'Storage updated successfully');
}
public function delete($password)
public function delete($password, $selectedActions = [])
{
$this->authorize('update', $this->resource);
if (! verifyPasswordConfirmation($password, $this)) {
return;
return 'The provided password is incorrect.';
}
$this->storage->delete();
$this->dispatch('refreshStorages');
return true;
}
}
+2 -1
View File
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Shared;
use App\Helpers\SshMultiplexingHelper;
use App\Models\Server;
use App\Support\ValidationPatterns;
use Livewire\Attributes\On;
use Livewire\Component;
@@ -36,7 +37,7 @@ class Terminal extends Component
if ($isContainer) {
// Validate container identifier format (alphanumeric, dashes, and underscores only)
if (! preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $identifier)) {
if (! ValidationPatterns::isValidContainerName($identifier)) {
throw new \InvalidArgumentException('Invalid container identifier format');
}