Merge branch 'next' into feat/prioritize-branch-selection

This commit is contained in:
Duane Adam authored and GitHub committed 2025-12-07 11:57:51 +08:00
commit 67c87324e5
118 files changed
+4563 -832

No files matched your search

@@ -18,6 +18,8 @@ class Show extends Component
public $isKeepAliveOn = true;
public bool $is_debug_enabled = false;
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
@@ -56,9 +58,23 @@ class Show extends Component
$this->application_deployment_queue = $application_deployment_queue;
$this->horizon_job_status = $this->application_deployment_queue->getHorizonJobStatus();
$this->deployment_uuid = $deploymentUuid;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->isKeepAliveOn();
}
public function toggleDebug()
{
try {
$this->authorize('update', $this->application);
$this->application->settings->is_debug_enabled = ! $this->application->settings->is_debug_enabled;
$this->application->settings->save();
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->application_deployment_queue->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function refreshQueue()
{
$this->application_deployment_queue->refresh();
+57 -25
View File
@@ -521,7 +521,7 @@ class General extends Component
}
}
public function loadComposeFile($isInit = false, $showToast = true)
public function loadComposeFile($isInit = false, $showToast = true, ?string $restoreBaseDirectory = null, ?string $restoreDockerComposeLocation = null)
{
try {
$this->authorize('update', $this->application);
@@ -530,7 +530,7 @@ class General extends Component
return;
}
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit, $restoreBaseDirectory, $restoreDockerComposeLocation);
if (is_null($this->parsedServices)) {
$showToast && $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
@@ -606,13 +606,6 @@ class General extends Component
}
}
public function updatedBaseDirectory()
{
if ($this->buildPack === 'dockercompose') {
$this->loadComposeFile();
}
}
public function updatedIsStatic($value)
{
if ($value) {
@@ -786,11 +779,13 @@ class General extends Component
try {
$this->authorize('update', $this->application);
$this->resetErrorBag();
$this->validate();
$oldPortsExposes = $this->application->ports_exposes;
$oldIsContainerLabelEscapeEnabled = $this->application->settings->is_container_label_escape_enabled;
$oldDockerComposeLocation = $this->initialDockerComposeLocation;
$oldBaseDirectory = $this->application->base_directory;
// Process FQDN with intermediate variable to avoid Collection/string confusion
$this->fqdn = str($this->fqdn)->replaceEnd(',', '')->trim()->toString();
@@ -821,6 +816,42 @@ class General extends Component
return; // Stop if there are conflicts and user hasn't confirmed
}
// Normalize paths BEFORE validation
if ($this->baseDirectory && $this->baseDirectory !== '/') {
$this->baseDirectory = rtrim($this->baseDirectory, '/');
$this->application->base_directory = $this->baseDirectory;
}
if ($this->publishDirectory && $this->publishDirectory !== '/') {
$this->publishDirectory = rtrim($this->publishDirectory, '/');
$this->application->publish_directory = $this->publishDirectory;
}
// Validate docker compose file path BEFORE saving to database
// This prevents invalid paths from being persisted when validation fails
if ($this->buildPack === 'dockercompose' &&
($oldDockerComposeLocation !== $this->dockerComposeLocation ||
$oldBaseDirectory !== $this->baseDirectory)) {
// Pass original values to loadComposeFile so it can restore them on failure
// The finally block in Application::loadComposeFile will save these original
// values if validation fails, preventing invalid paths from being persisted
$compose_return = $this->loadComposeFile(
isInit: false,
showToast: false,
restoreBaseDirectory: $oldBaseDirectory,
restoreDockerComposeLocation: $oldDockerComposeLocation
);
if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) {
// Validation failed - restore original values to component properties
$this->baseDirectory = $oldBaseDirectory;
$this->dockerComposeLocation = $oldDockerComposeLocation;
// The model was saved by loadComposeFile's finally block with original values
// Refresh to sync component with database state
$this->application->refresh();
return;
}
}
$this->application->save();
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
@@ -828,13 +859,6 @@ class General extends Component
$this->application->save();
}
if ($this->buildPack === 'dockercompose' && $oldDockerComposeLocation !== $this->dockerComposeLocation) {
$compose_return = $this->loadComposeFile(showToast: false);
if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) {
return;
}
}
if ($oldPortsExposes !== $this->portsExposes || $oldIsContainerLabelEscapeEnabled !== $this->isContainerLabelEscapeEnabled) {
$this->resetDefaultLabels();
}
@@ -855,14 +879,6 @@ class General extends Component
$this->application->ports_exposes = $port;
}
}
if ($this->baseDirectory && $this->baseDirectory !== '/') {
$this->baseDirectory = rtrim($this->baseDirectory, '/');
$this->application->base_directory = $this->baseDirectory;
}
if ($this->publishDirectory && $this->publishDirectory !== '/') {
$this->publishDirectory = rtrim($this->publishDirectory, '/');
$this->application->publish_directory = $this->publishDirectory;
}
if ($this->buildPack === 'dockercompose') {
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
if ($this->application->isDirty('docker_compose_domains')) {
@@ -1018,11 +1034,27 @@ class General extends Component
// Use relative path for clarity in preview (e.g., ./backend/docker-compose.yaml)
// Actual deployment uses absolute path: /artifacts/{deployment_uuid}{base_directory}{docker_compose_location}
// Build-time env path references ApplicationDeploymentJob::BUILD_TIME_ENV_PATH as source of truth
return injectDockerComposeFlags(
$command = injectDockerComposeFlags(
$this->dockerComposeCustomBuildCommand,
".{$normalizedBase}{$this->dockerComposeLocation}",
\App\Jobs\ApplicationDeploymentJob::BUILD_TIME_ENV_PATH
);
// Inject build args if not using build secrets
if (! $this->application->settings->use_build_secrets) {
$buildTimeEnvs = $this->application->environment_variables()
->where('is_buildtime', true)
->get();
if ($buildTimeEnvs->isNotEmpty()) {
$buildArgs = generateDockerBuildArgs($buildTimeEnvs);
$buildArgsString = $buildArgs->implode(' ');
$command = injectDockerComposeBuildArgs($command, $buildArgsString);
}
}
return $command;
}
public function getDockerComposeStartCommandPreviewProperty(): string
+24 -4
View File
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application;
use App\Models\Application;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -19,9 +20,30 @@ class Rollback extends Component
public array $parameters;
#[Validate(['integer', 'min:0', 'max:100'])]
public int $dockerImagesToKeep = 2;
public bool $serverRetentionDisabled = false;
public function mount()
{
$this->parameters = get_route_parameters();
$this->dockerImagesToKeep = $this->application->settings->docker_images_to_keep ?? 2;
$server = $this->application->destination->server;
$this->serverRetentionDisabled = $server->settings->disable_application_image_retention ?? false;
}
public function saveSettings()
{
try {
$this->authorize('update', $this->application);
$this->validate();
$this->application->settings->docker_images_to_keep = $this->dockerImagesToKeep;
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function rollbackImage($commit)
@@ -66,14 +88,12 @@ class Rollback extends Component
return str($item)->contains($image);
})->map(function ($item) {
$item = str($item)->explode('#');
if ($item[1] === $this->current) {
// $is_current = true;
}
$is_current = $item[1] === $this->current;
return [
'tag' => $item[1],
'created_at' => $item[2],
'is_current' => $is_current ?? null,
'is_current' => $is_current,
];
})->toArray();
}
@@ -75,16 +75,6 @@ class GithubPrivateRepository extends Component
$this->github_apps = GithubApp::private();
}
public function updatedBaseDirectory()
{
if ($this->base_directory) {
$this->base_directory = rtrim($this->base_directory, '/');
if (! str($this->base_directory)->startsWith('/')) {
$this->base_directory = '/'.$this->base_directory;
}
}
}
public function updatedBuildPack()
{
if ($this->build_pack === 'nixpacks') {
@@ -107,26 +107,6 @@ class PublicGitRepository extends Component
$this->query = request()->query();
}
public function updatedBaseDirectory()
{
if ($this->base_directory) {
$this->base_directory = rtrim($this->base_directory, '/');
if (! str($this->base_directory)->startsWith('/')) {
$this->base_directory = '/'.$this->base_directory;
}
}
}
public function updatedDockerComposeLocation()
{
if ($this->docker_compose_location) {
$this->docker_compose_location = rtrim($this->docker_compose_location, '/');
if (! str($this->docker_compose_location)->startsWith('/')) {
$this->docker_compose_location = '/'.$this->docker_compose_location;
}
}
}
public function updatedBuildPack()
{
if ($this->build_pack === 'nixpacks') {
@@ -82,6 +82,21 @@ class ServiceApplicationView extends Component
}
}
public function instantSaveSettings()
{
try {
$this->authorize('update', $this->application);
// Save checkbox states without port validation
$this->application->is_gzip_enabled = $this->isGzipEnabled;
$this->application->is_stripprefix_enabled = $this->isStripprefixEnabled;
$this->application->exclude_from_status = $this->excludeFromStatus;
$this->application->save();
$this->dispatch('success', 'Settings saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSaveAdvanced()
{
try {
+27 -2
View File
@@ -39,10 +39,14 @@ class GetLogs extends Component
public ?bool $streamLogs = false;
public ?bool $showTimeStamps = false;
public ?bool $showTimeStamps = true;
public ?int $numberOfLines = 100;
public bool $expandByDefault = false;
public bool $collapsible = true;
public function mount()
{
if (! is_null($this->resource)) {
@@ -92,12 +96,33 @@ class GetLogs extends Component
}
}
public function toggleTimestamps()
{
$previousValue = $this->showTimeStamps;
$this->showTimeStamps = ! $this->showTimeStamps;
try {
$this->instantSave();
$this->getLogs(true);
} catch (\Throwable $e) {
// Revert the flag to its previous value on failure
$this->showTimeStamps = $previousValue;
return handleError($e, $this);
}
}
public function toggleStreamLogs()
{
$this->streamLogs = ! $this->streamLogs;
}
public function getLogs($refresh = false)
{
if (! $this->server->isFunctional()) {
return;
}
if (! $refresh && ($this->resource?->getMorphClass() === \App\Models\Service::class || str($this->container)->contains('-pr-'))) {
if (! $refresh && ! $this->expandByDefault && ($this->resource?->getMorphClass() === \App\Models\Service::class || str($this->container)->contains('-pr-'))) {
return;
}
if ($this->numberOfLines <= 0 || is_null($this->numberOfLines)) {
@@ -41,7 +41,7 @@ class Add extends Component
'command' => 'required|string',
'frequency' => 'required|string',
'container' => 'nullable|string',
'timeout' => 'required|integer|min:60|max:3600',
'timeout' => 'required|integer|min:60|max:36000',
];
protected $validationAttributes = [
@@ -40,7 +40,7 @@ class Show extends Component
#[Validate(['string', 'nullable'])]
public ?string $container = null;
#[Validate(['integer', 'required', 'min:60', 'max:3600'])]
#[Validate(['integer', 'required', 'min:60', 'max:36000'])]
public $timeout = 300;
#[Locked]