mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 04:18:34 +00:00
Merge remote-tracking branch 'origin/next' into feat/noindex-domains
This commit is contained in:
commit
003e44d123
1274 files changed
+156179
-25249
No files matched your search
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Application\Backup;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\LocalFileVolume;
|
||||
use App\Models\LocalPersistentVolume;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
|
||||
class Create extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
#[Locked]
|
||||
public Application $application;
|
||||
|
||||
public ?string $selectedTargetKey = null;
|
||||
|
||||
public ?string $targetKey = null;
|
||||
|
||||
public bool $targetLocked = false;
|
||||
|
||||
public string $frequency = 'daily';
|
||||
|
||||
public Collection $targets;
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'targetKey' => ['required', 'string', 'regex:/^(volume|directory):[1-9][0-9]*$/'],
|
||||
'frequency' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->authorize('view', $this->application);
|
||||
$this->targetLocked = $this->selectedTargetKey !== null;
|
||||
$this->targetKey = $this->selectedTargetKey;
|
||||
|
||||
// When opened from a volume row the target is fixed — skip listing every volume/directory.
|
||||
if ($this->targetLocked && is_string($this->selectedTargetKey)) {
|
||||
$target = $this->selectedTarget();
|
||||
if ($target instanceof LocalPersistentVolume) {
|
||||
$this->targets = collect([[
|
||||
'key' => 'volume:'.$target->id,
|
||||
'type' => 'Volume',
|
||||
'name' => $target->name,
|
||||
]]);
|
||||
} elseif ($target instanceof LocalFileVolume) {
|
||||
$this->targets = collect([[
|
||||
'key' => 'directory:'.$target->id,
|
||||
'type' => 'Directory',
|
||||
'name' => $target->fs_path,
|
||||
]]);
|
||||
} else {
|
||||
$this->targets = collect();
|
||||
}
|
||||
$this->loadSelectedBackup();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$volumes = $this->application->persistentStorages()
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->map(fn (LocalPersistentVolume $volume): array => [
|
||||
'key' => 'volume:'.$volume->id,
|
||||
'type' => 'Volume',
|
||||
'name' => $volume->name,
|
||||
]);
|
||||
$directories = $this->application->fileStorages()
|
||||
->where('is_directory', true)
|
||||
->where('is_host_file', false)
|
||||
->orderBy('fs_path')
|
||||
->get()
|
||||
->map(fn (LocalFileVolume $directory): array => [
|
||||
'key' => 'directory:'.$directory->id,
|
||||
'type' => 'Directory',
|
||||
'name' => $directory->fs_path,
|
||||
]);
|
||||
$this->targets = $volumes->concat($directories)->values();
|
||||
$this->targetKey = $this->selectedTargetKey ?? data_get($this->targets->first(), 'key');
|
||||
$this->loadSelectedBackup();
|
||||
}
|
||||
|
||||
public function updatedTargetKey(): void
|
||||
{
|
||||
$this->loadSelectedBackup();
|
||||
}
|
||||
|
||||
public function submit(): void
|
||||
{
|
||||
$this->authorize('update', $this->application);
|
||||
$this->validate();
|
||||
|
||||
$target = $this->selectedTarget();
|
||||
if (! $target) {
|
||||
$this->addError('targetKey', 'Select a volume or directory owned by this application.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! validate_cron_expression($this->frequency)) {
|
||||
$this->addError('frequency', 'The frequency must be a valid cron or human expression.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$backup = $target->scheduledBackups()->updateOrCreate(
|
||||
[],
|
||||
[
|
||||
'team_id' => currentTeam()->id,
|
||||
'frequency' => $this->frequency,
|
||||
'enabled' => true,
|
||||
],
|
||||
);
|
||||
|
||||
$this->dispatch('success', $backup->wasRecentlyCreated ? 'Scheduled storage backup created.' : 'Scheduled storage backup updated.');
|
||||
redirectRoute($this, 'project.application.backup.show', [
|
||||
'project_uuid' => $this->application->project()->uuid,
|
||||
'environment_uuid' => $this->application->environment->uuid,
|
||||
'application_uuid' => $this->application->uuid,
|
||||
'backup_uuid' => $backup->uuid,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.application.backup.create');
|
||||
}
|
||||
|
||||
private function loadSelectedBackup(): void
|
||||
{
|
||||
$target = $this->selectedTarget();
|
||||
if (! $target) {
|
||||
return;
|
||||
}
|
||||
|
||||
$backup = $target->scheduledBackups()->first();
|
||||
if ($backup) {
|
||||
$this->frequency = $backup->frequency;
|
||||
}
|
||||
}
|
||||
|
||||
private function selectedTarget(): LocalPersistentVolume|LocalFileVolume|null
|
||||
{
|
||||
[$type, $id] = array_pad(explode(':', (string) $this->targetKey, 2), 2, null);
|
||||
if (! ctype_digit((string) $id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
'volume' => $this->application->persistentStorages()->whereKey((int) $id)->first(),
|
||||
'directory' => $this->application->fileStorages()
|
||||
->whereKey((int) $id)
|
||||
->where('is_directory', true)
|
||||
->where('is_host_file', false)
|
||||
->first(),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Application\Backup;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ScheduledVolumeBackup;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public Application $application;
|
||||
|
||||
public array $parameters;
|
||||
|
||||
public string $search = '';
|
||||
|
||||
protected $listeners = ['refreshVolumeBackups' => '$refresh'];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->application = $this->findApplication();
|
||||
$this->authorize('view', $this->application);
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->search = request()->string('search')->toString();
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$backups = ScheduledVolumeBackup::query()
|
||||
->with(['backupable', 'latestExecution', 's3'])
|
||||
->withCount('executions')
|
||||
->forApplication($this->application)
|
||||
->latest()
|
||||
->get();
|
||||
|
||||
return view('livewire.project.application.backup.index', ['backups' => $backups]);
|
||||
}
|
||||
|
||||
private function findApplication(): Application
|
||||
{
|
||||
$project = currentTeam()->projects()
|
||||
->where('uuid', request()->route('project_uuid'))
|
||||
->firstOrFail();
|
||||
$environment = $project->environments()
|
||||
->where('uuid', request()->route('environment_uuid'))
|
||||
->firstOrFail();
|
||||
|
||||
return $environment->applications()
|
||||
->with('destination.server')
|
||||
->where('uuid', request()->route('application_uuid'))
|
||||
->firstOrFail();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Application\Backup;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ScheduledVolumeBackup;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public Application $application;
|
||||
|
||||
public ScheduledVolumeBackup $backup;
|
||||
|
||||
public array $parameters;
|
||||
|
||||
public string $section = 'general';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$project = currentTeam()->projects()
|
||||
->where('uuid', request()->route('project_uuid'))
|
||||
->firstOrFail();
|
||||
$environment = $project->environments()
|
||||
->where('uuid', request()->route('environment_uuid'))
|
||||
->firstOrFail();
|
||||
$this->application = $environment->applications()
|
||||
->with('destination.server')
|
||||
->where('uuid', request()->route('application_uuid'))
|
||||
->firstOrFail();
|
||||
$this->authorize('view', $this->application);
|
||||
|
||||
$this->backup = ScheduledVolumeBackup::query()
|
||||
->with('backupable')
|
||||
->where('uuid', request()->route('backup_uuid'))
|
||||
->forApplication($this->application)
|
||||
->firstOrFail();
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->section = match (request()->route()?->getName()) {
|
||||
'project.application.backup.s3' => 's3',
|
||||
'project.application.backup.retention' => 'retention',
|
||||
'project.application.backup.executions' => 'executions',
|
||||
'project.application.backup.danger' => 'danger',
|
||||
default => 'general',
|
||||
};
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.application.backup.show');
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class Configuration extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->currentRoute = request()->route()->getName();
|
||||
$this->syncCurrentRoute();
|
||||
|
||||
$project = currentTeam()
|
||||
->projects()
|
||||
@@ -36,10 +36,14 @@ class Configuration extends Component
|
||||
->where('uuid', request()->route('environment_uuid'))
|
||||
->firstOrFail();
|
||||
$application = $environment->applications()
|
||||
->with(['destination'])
|
||||
->with(['destination.server', 'environment.project'])
|
||||
->where('uuid', request()->route('application_uuid'))
|
||||
->firstOrFail();
|
||||
|
||||
// Parent page already resolved these; keep them on the model for nested components.
|
||||
$application->setRelation('environment', $environment);
|
||||
$environment->setRelation('project', $project);
|
||||
|
||||
$this->project = $project;
|
||||
$this->environment = $environment;
|
||||
$this->application = $application;
|
||||
@@ -49,8 +53,23 @@ class Configuration extends Component
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep sidebar active state in sync on full-page navigations.
|
||||
* Ignore Livewire update requests so poll/refresh does not clear it.
|
||||
*/
|
||||
protected function syncCurrentRoute(): void
|
||||
{
|
||||
$routeName = request()->route()?->getName();
|
||||
|
||||
if (is_string($routeName) && str_starts_with($routeName, 'project.application.')) {
|
||||
$this->currentRoute = $routeName;
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$this->syncCurrentRoute();
|
||||
|
||||
return view('livewire.project.application.configuration');
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Livewire\Project\Application\Deployment;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -28,6 +30,24 @@ class Index extends Component
|
||||
|
||||
public ?string $pull_request_id = null;
|
||||
|
||||
public array $pullRequestOptions = [];
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public array $deploymentFilters = [];
|
||||
|
||||
public string $deploymentSort = 'newest';
|
||||
|
||||
public array $statusFilterOptions = [];
|
||||
|
||||
public array $sourceFilterOptions = [];
|
||||
|
||||
public array $serverFilterOptions = [];
|
||||
|
||||
public bool $embedded = false;
|
||||
|
||||
public ?string $selectedDeploymentUuid = null;
|
||||
|
||||
protected $queryString = ['pull_request_id'];
|
||||
|
||||
public function getListeners()
|
||||
@@ -64,11 +84,26 @@ class Index extends Component
|
||||
}
|
||||
}
|
||||
|
||||
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, $this->defaultTake, $this->pull_request_id);
|
||||
$this->application = $application;
|
||||
if ($this->embedded) {
|
||||
$this->defaultTake = 3;
|
||||
}
|
||||
$this->loadPullRequestOptions();
|
||||
$this->loadDeploymentFilterOptions();
|
||||
['deployments' => $deployments, 'count' => $count] = $application->deployments(
|
||||
search: $this->search,
|
||||
filters: $this->deploymentFilters,
|
||||
sort: $this->deploymentSort,
|
||||
take: $this->defaultTake,
|
||||
pullRequestId: $this->pull_request_id,
|
||||
);
|
||||
$this->deployments = $deployments;
|
||||
$this->deployments_count = $count;
|
||||
$this->current_url = url()->current();
|
||||
$this->current_url = route('project.application.deployment.index', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_uuid' => $environment->uuid,
|
||||
'application_uuid' => $application->uuid,
|
||||
]);
|
||||
$this->updateCurrentPage();
|
||||
$this->showMore();
|
||||
}
|
||||
@@ -90,38 +125,94 @@ class Index extends Component
|
||||
$this->loadDeployments();
|
||||
}
|
||||
|
||||
public function previousPage(?int $take = null)
|
||||
public function previousPage(): void
|
||||
{
|
||||
if ($take) {
|
||||
$this->skip = $this->skip - $take;
|
||||
}
|
||||
$this->skip = $this->skip - $this->defaultTake;
|
||||
if ($this->skip < 0) {
|
||||
$this->showPrev = false;
|
||||
$this->skip = 0;
|
||||
}
|
||||
$this->skip = max(0, $this->skip - $this->defaultTake);
|
||||
$this->showPrev = $this->skip > 0;
|
||||
$this->updateCurrentPage();
|
||||
$this->loadDeployments();
|
||||
}
|
||||
|
||||
public function nextPage(?int $take = null)
|
||||
public function nextPage(): void
|
||||
{
|
||||
if ($take) {
|
||||
$this->skip = $this->skip + $take;
|
||||
}
|
||||
$this->showPrev = true;
|
||||
$this->goToPage($this->currentPage + 1);
|
||||
}
|
||||
|
||||
public function goToPage(int $page): void
|
||||
{
|
||||
$lastPage = max(1, (int) ceil($this->deployments_count / $this->defaultTake));
|
||||
$page = max(1, min($page, $lastPage));
|
||||
$this->skip = ($page - 1) * $this->defaultTake;
|
||||
$this->showPrev = $page > 1;
|
||||
$this->updateCurrentPage();
|
||||
$this->loadDeployments();
|
||||
}
|
||||
|
||||
public function loadDeployments()
|
||||
{
|
||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $this->defaultTake, $this->pull_request_id);
|
||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments(
|
||||
skip: $this->skip,
|
||||
take: $this->defaultTake,
|
||||
pullRequestId: $this->pull_request_id,
|
||||
search: $this->search,
|
||||
filters: $this->deploymentFilters,
|
||||
sort: $this->deploymentSort,
|
||||
);
|
||||
$this->deployments = $deployments;
|
||||
$this->deployments_count = $count;
|
||||
$this->showMore();
|
||||
}
|
||||
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
$this->resetPaginationAndLoad();
|
||||
}
|
||||
|
||||
public function toggleDeploymentFilter(string $filter): void
|
||||
{
|
||||
$validFilters = collect($this->statusFilterOptions)
|
||||
->concat($this->sourceFilterOptions)
|
||||
->concat($this->serverFilterOptions)
|
||||
->pluck('value')
|
||||
->values();
|
||||
|
||||
if (! $validFilters->contains($filter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($filter, $this->deploymentFilters, true)) {
|
||||
$this->deploymentFilters = array_values(array_diff($this->deploymentFilters, [$filter]));
|
||||
} else {
|
||||
$this->deploymentFilters[] = $filter;
|
||||
}
|
||||
|
||||
$this->pull_request_id = null;
|
||||
$this->resetPaginationAndLoad();
|
||||
}
|
||||
|
||||
public function setPullRequestFilter(string $pullRequestId): void
|
||||
{
|
||||
$validPullRequestIds = collect($this->pullRequestOptions)->pluck('value');
|
||||
|
||||
if (! $validPullRequestIds->contains($pullRequestId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pull_request_id = $pullRequestId === '' ? null : $pullRequestId;
|
||||
$this->deploymentFilters = [];
|
||||
$this->resetPaginationAndLoad();
|
||||
}
|
||||
|
||||
public function setDeploymentSort(string $sort): void
|
||||
{
|
||||
if (! in_array($sort, ['newest', 'oldest'], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->deploymentSort = $sort;
|
||||
$this->resetPaginationAndLoad();
|
||||
}
|
||||
|
||||
public function updatedPullRequestId($value)
|
||||
{
|
||||
// Sanitize and validate the pull request ID
|
||||
@@ -139,20 +230,15 @@ class Index extends Component
|
||||
$this->pull_request_id = null;
|
||||
}
|
||||
|
||||
// Reset pagination when filter changes
|
||||
$this->skip = 0;
|
||||
$this->showPrev = false;
|
||||
$this->updateCurrentPage();
|
||||
$this->loadDeployments();
|
||||
$this->deploymentFilters = [];
|
||||
$this->resetPaginationAndLoad();
|
||||
}
|
||||
|
||||
public function clearFilter()
|
||||
{
|
||||
$this->pull_request_id = null;
|
||||
$this->skip = 0;
|
||||
$this->showPrev = false;
|
||||
$this->updateCurrentPage();
|
||||
$this->loadDeployments();
|
||||
$this->deploymentFilters = [];
|
||||
$this->resetPaginationAndLoad();
|
||||
}
|
||||
|
||||
private function updateCurrentPage()
|
||||
@@ -160,6 +246,116 @@ class Index extends Component
|
||||
$this->currentPage = intval($this->skip / $this->defaultTake) + 1;
|
||||
}
|
||||
|
||||
private function loadPullRequestOptions(): void
|
||||
{
|
||||
$pullRequestIds = ApplicationDeploymentQueue::query()
|
||||
->where('application_id', $this->application->id)
|
||||
->where('pull_request_id', '>', 0)
|
||||
->distinct()
|
||||
->orderByDesc('pull_request_id')
|
||||
->pluck('pull_request_id')
|
||||
->map(fn ($pullRequestId) => (string) $pullRequestId)
|
||||
->values();
|
||||
|
||||
if ($this->pull_request_id && ! $pullRequestIds->contains($this->pull_request_id)) {
|
||||
$this->pull_request_id = null;
|
||||
}
|
||||
|
||||
$this->pullRequestOptions = collect([
|
||||
['value' => '', 'label' => 'All deployments'],
|
||||
])
|
||||
->concat($pullRequestIds->map(fn ($pullRequestId) => [
|
||||
'value' => $pullRequestId,
|
||||
'label' => "Pull request #{$pullRequestId}",
|
||||
]))
|
||||
->all();
|
||||
}
|
||||
|
||||
private function loadDeploymentFilterOptions(): void
|
||||
{
|
||||
$statuses = ApplicationDeploymentQueue::query()
|
||||
->where('application_id', $this->application->id)
|
||||
->distinct()
|
||||
->pluck('status');
|
||||
|
||||
$statusLabels = [
|
||||
ApplicationDeploymentStatus::FINISHED->value => 'Success',
|
||||
ApplicationDeploymentStatus::FAILED->value => 'Failed',
|
||||
ApplicationDeploymentStatus::IN_PROGRESS->value => 'In progress',
|
||||
ApplicationDeploymentStatus::QUEUED->value => 'Queued',
|
||||
ApplicationDeploymentStatus::CANCELLED_BY_USER->value => 'Cancelled',
|
||||
];
|
||||
|
||||
$this->statusFilterOptions = collect($statusLabels)
|
||||
->filter(fn (string $label, string $status) => $statuses->contains($status))
|
||||
->map(fn (string $label, string $status) => [
|
||||
'value' => "status:{$status}",
|
||||
'label' => $label,
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$sourceKeys = ApplicationDeploymentQueue::query()
|
||||
->where('application_id', $this->application->id)
|
||||
->selectRaw("
|
||||
CASE
|
||||
WHEN pull_request_id > 0 THEN 'pull-request'
|
||||
WHEN is_webhook THEN 'webhook'
|
||||
WHEN rollback THEN 'rollback'
|
||||
WHEN is_api THEN 'api'
|
||||
ELSE 'manual'
|
||||
END AS source_key
|
||||
")
|
||||
->distinct()
|
||||
->pluck('source_key');
|
||||
|
||||
$sourceLabels = [
|
||||
'manual' => 'Manual',
|
||||
'pull-request' => 'Pull requests',
|
||||
'webhook' => 'Webhooks',
|
||||
'api' => 'API',
|
||||
'rollback' => 'Rollbacks',
|
||||
];
|
||||
|
||||
$this->sourceFilterOptions = collect($sourceLabels)
|
||||
->filter(fn (string $label, string $source) => $sourceKeys->contains($source))
|
||||
->map(fn (string $label, string $source) => [
|
||||
'value' => "source:{$source}",
|
||||
'label' => $label,
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$servers = ApplicationDeploymentQueue::query()
|
||||
->where('application_id', $this->application->id)
|
||||
->whereNotNull('server_id')
|
||||
->select(['server_id', 'server_name'])
|
||||
->distinct()
|
||||
->orderBy('server_name')
|
||||
->get()
|
||||
->unique('server_id');
|
||||
|
||||
$this->serverFilterOptions = $servers
|
||||
->map(function (ApplicationDeploymentQueue $deployment): array {
|
||||
$serverId = (int) $deployment->server_id;
|
||||
|
||||
return [
|
||||
'value' => "server:{$serverId}",
|
||||
'label' => $deployment->server_name ?: "Server #{$serverId}",
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function resetPaginationAndLoad(): void
|
||||
{
|
||||
$this->skip = 0;
|
||||
$this->showPrev = false;
|
||||
$this->updateCurrentPage();
|
||||
$this->loadDeployments();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.application.deployment.index');
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -97,6 +97,9 @@ class General extends Component
|
||||
|
||||
public bool $isSpa = false;
|
||||
|
||||
/** UI-only aggregate of isStatic/isSpa: dynamic | static | spa */
|
||||
public string $siteType = 'dynamic';
|
||||
|
||||
public bool $isBuildServerEnabled = false;
|
||||
|
||||
public bool $isPreserveRepositoryEnabled = false;
|
||||
@@ -306,12 +309,7 @@ class General extends Component
|
||||
}
|
||||
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
||||
// Convert service names with dots and dashes to use underscores for HTML form binding
|
||||
$sanitizedDomains = [];
|
||||
foreach ($this->parsedServiceDomains as $serviceName => $domain) {
|
||||
$sanitizedKey = str($serviceName)->replace('-', '_')->replace('.', '_')->toString();
|
||||
$sanitizedDomains[$sanitizedKey] = $domain;
|
||||
}
|
||||
$this->parsedServiceDomains = $sanitizedDomains;
|
||||
$this->parsedServiceDomains = $this->sanitizeParsedServiceDomainsForForm($this->parsedServiceDomains);
|
||||
|
||||
$this->customLabels = $this->application->parseContainerLabels();
|
||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && $this->application->settings->is_container_label_readonly_enabled === true) {
|
||||
@@ -449,6 +447,7 @@ class General extends Component
|
||||
// Application settings properties
|
||||
$this->isStatic = $this->application->settings->is_static;
|
||||
$this->isSpa = $this->application->settings->is_spa;
|
||||
$this->siteType = $this->isStatic ? ($this->isSpa ? 'spa' : 'static') : 'dynamic';
|
||||
$this->isBuildServerEnabled = $this->application->settings->is_build_server_enabled;
|
||||
$this->isPreserveRepositoryEnabled = $this->application->settings->is_preserve_repository_enabled;
|
||||
$this->isContainerLabelEscapeEnabled = $this->application->settings->is_container_label_escape_enabled;
|
||||
@@ -456,6 +455,13 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function setSiteType(): void
|
||||
{
|
||||
$this->isStatic = $this->siteType !== 'dynamic';
|
||||
$this->isSpa = $this->siteType === 'spa';
|
||||
$this->instantSave();
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
@@ -527,12 +533,7 @@ class General extends Component
|
||||
|
||||
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
||||
// Convert service names with dots and dashes to use underscores for HTML form binding
|
||||
$sanitizedDomains = [];
|
||||
foreach ($this->parsedServiceDomains as $serviceName => $domain) {
|
||||
$sanitizedKey = str($serviceName)->replace('-', '_')->replace('.', '_')->toString();
|
||||
$sanitizedDomains[$sanitizedKey] = $domain;
|
||||
}
|
||||
$this->parsedServiceDomains = $sanitizedDomains;
|
||||
$this->parsedServiceDomains = $this->sanitizeParsedServiceDomainsForForm($this->parsedServiceDomains);
|
||||
|
||||
$showToast && $this->dispatch('success', 'Docker compose file loaded.');
|
||||
$this->dispatch('compose_loaded');
|
||||
@@ -558,22 +559,14 @@ class General extends Component
|
||||
|
||||
$uuid = new_public_id();
|
||||
$domain = generateUrl(server: $this->application->destination->server, random: $uuid);
|
||||
$sanitizedKey = str($serviceName)->replace('-', '_')->replace('.', '_')->toString();
|
||||
$sanitizedKey = normalizeComposeServiceName($serviceName);
|
||||
$this->parsedServiceDomains[$sanitizedKey]['domain'] = $domain;
|
||||
|
||||
// Convert back to original service names for storage
|
||||
$originalDomains = [];
|
||||
$composeServiceNames = collect(data_get($this->parsedServices, 'services', []))->keys()->map(fn ($name) => (string) $name)->values()->all();
|
||||
foreach ($this->parsedServiceDomains as $key => $value) {
|
||||
// Find the original service name by checking parsed services
|
||||
$originalServiceName = $key;
|
||||
if (isset($this->parsedServices['services'])) {
|
||||
foreach ($this->parsedServices['services'] as $originalName => $service) {
|
||||
if (str($originalName)->replace('-', '_')->replace('.', '_')->toString() === $key) {
|
||||
$originalServiceName = $originalName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$originalServiceName = findComposeServiceName((string) $key, $composeServiceNames) ?? (string) $key;
|
||||
$originalDomains[$originalServiceName] = $value;
|
||||
}
|
||||
|
||||
@@ -705,7 +698,10 @@ class General extends Component
|
||||
if ($this->application->additional_servers->count() === 0) {
|
||||
foreach ($domains as $domain) {
|
||||
if (! validateDNSEntry($domain, $this->application->destination->server)) {
|
||||
$showToaster && $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<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.");
|
||||
$server = $this->application->destination->server;
|
||||
$target = serverDnsTargetIp($server) ?? $server->ip;
|
||||
$guidance = dnsMismatchGuidanceMessage($target, $target);
|
||||
$showToaster && $this->dispatch('error', 'Validating DNS failed.', "{$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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -767,7 +763,10 @@ class General extends Component
|
||||
$this->application->redirect = $this->redirect;
|
||||
$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).');
|
||||
$server = $this->application->destination?->server;
|
||||
$target = $server ? (serverDnsTargetIp($server) ?? $server->ip) : null;
|
||||
$dnsHint = dnsMismatchGuidanceMessage($target, $target);
|
||||
$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 ({$dnsHint}).");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -885,13 +884,17 @@ class General extends Component
|
||||
foreach ($this->parsedServiceDomains as $serviceName => $service) {
|
||||
$this->parsedServiceDomains[$serviceName]['domain'] = ValidationPatterns::normalizeApplicationDomains(data_get($service, 'domain'));
|
||||
}
|
||||
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||
$originalDomains = $this->composeDomainsForStorage();
|
||||
$this->application->docker_compose_domains = json_encode($originalDomains);
|
||||
if ($this->application->isDirty('docker_compose_domains')) {
|
||||
foreach ($this->parsedServiceDomains as $service) {
|
||||
foreach ($originalDomains as $service) {
|
||||
$domain = data_get($service, 'domain');
|
||||
if ($domain) {
|
||||
if (! validateDNSEntry($domain, $this->application->destination->server)) {
|
||||
$showToaster && $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<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.");
|
||||
$server = $this->application->destination->server;
|
||||
$target = serverDnsTargetIp($server) ?? $server->ip;
|
||||
$guidance = dnsMismatchGuidanceMessage($target, $target);
|
||||
$showToaster && $this->dispatch('error', 'Validating DNS failed.', "{$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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1012,4 +1015,58 @@ class General extends Component
|
||||
'{workdir}/.env'
|
||||
);
|
||||
}
|
||||
|
||||
private function composeDomainsForStorage(): array
|
||||
{
|
||||
return rekeyComposeDomainsToServiceNames(
|
||||
$this->parsedServiceDomains,
|
||||
collect(data_get($this->parsedServices, 'services', []))->keys(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse domain map keys to underscore form keys for Livewire/HTML binding.
|
||||
* When twin keys exist, prefer a filled domain over a blank one.
|
||||
*
|
||||
* @param array<string, mixed> $domains
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function sanitizeParsedServiceDomainsForForm(array $domains): array
|
||||
{
|
||||
$sanitizedDomains = [];
|
||||
|
||||
foreach ($domains as $serviceName => $domain) {
|
||||
$sanitizedKey = normalizeComposeServiceName((string) $serviceName);
|
||||
if (! array_key_exists($sanitizedKey, $sanitizedDomains)) {
|
||||
$sanitizedDomains[$sanitizedKey] = $domain;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing = $sanitizedDomains[$sanitizedKey];
|
||||
if (is_object($existing)) {
|
||||
$existing = (array) $existing;
|
||||
}
|
||||
if (is_object($domain)) {
|
||||
$domain = (array) $domain;
|
||||
}
|
||||
if (! is_array($existing)) {
|
||||
$existing = ['domain' => $existing];
|
||||
}
|
||||
if (! is_array($domain)) {
|
||||
$domain = ['domain' => $domain];
|
||||
}
|
||||
|
||||
$merged = array_merge($existing, $domain);
|
||||
$merged['domain'] = preferComposeDomainValue(
|
||||
$existing['domain'] ?? null,
|
||||
false,
|
||||
$domain['domain'] ?? null,
|
||||
false,
|
||||
);
|
||||
$sanitizedDomains[$sanitizedKey] = $merged;
|
||||
}
|
||||
|
||||
return $sanitizedDomains;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ class Heading extends Component
|
||||
|
||||
public array $parameters;
|
||||
|
||||
public string $activeRouteName = '';
|
||||
|
||||
protected string $deploymentUuid;
|
||||
|
||||
public bool $docker_cleanup = true;
|
||||
@@ -38,6 +40,7 @@ class Heading extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->syncActiveRouteName();
|
||||
$this->parameters = [
|
||||
'project_uuid' => $this->application->project()->uuid,
|
||||
'environment_uuid' => $this->application->environment->uuid,
|
||||
@@ -48,6 +51,20 @@ class Heading extends Component
|
||||
$this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep the active tab in sync with the real page route.
|
||||
* Only update when the request is a full page route (not livewire.update),
|
||||
* so wire:poll re-renders do not wipe the highlighted tab.
|
||||
*/
|
||||
protected function syncActiveRouteName(): void
|
||||
{
|
||||
$routeName = request()->route()?->getName();
|
||||
|
||||
if (is_string($routeName) && str_starts_with($routeName, 'project.application.')) {
|
||||
$this->activeRouteName = $routeName;
|
||||
}
|
||||
}
|
||||
|
||||
public function checkStatus()
|
||||
{
|
||||
if ($this->application->destination->server->isFunctional()) {
|
||||
@@ -115,12 +132,12 @@ class Heading extends Component
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->redirectRoute('project.application.deployment.show', [
|
||||
return redirectRoute($this, 'project.application.deployment.show', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deploymentUuid,
|
||||
'environment_uuid' => $this->parameters['environment_uuid'],
|
||||
], navigate: false);
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -172,12 +189,12 @@ class Heading extends Component
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->redirectRoute('project.application.deployment.show', [
|
||||
return redirectRoute($this, 'project.application.deployment.show', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deploymentUuid,
|
||||
'environment_uuid' => $this->parameters['environment_uuid'],
|
||||
], navigate: false);
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -185,6 +202,8 @@ class Heading extends Component
|
||||
|
||||
public function render()
|
||||
{
|
||||
$this->syncActiveRouteName();
|
||||
|
||||
return view('livewire.project.application.heading', [
|
||||
'checkboxes' => [
|
||||
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class InternalAccess extends Component
|
||||
{
|
||||
public Application $application;
|
||||
|
||||
public ?string $currentInternalHostname = null;
|
||||
|
||||
public bool $currentInternalHostnameLoaded = false;
|
||||
|
||||
public function loadCurrentInternalHostname(): void
|
||||
{
|
||||
try {
|
||||
$containers = getCurrentApplicationContainerStatus(
|
||||
$this->application->destination->server,
|
||||
$this->application->id,
|
||||
0
|
||||
);
|
||||
$currentContainer = $containers->first(
|
||||
fn ($container) => data_get($container, 'State') === 'running'
|
||||
) ?? $containers->first();
|
||||
|
||||
$this->currentInternalHostname = data_get($currentContainer, 'Names');
|
||||
} catch (\Throwable) {
|
||||
$this->currentInternalHostname = null;
|
||||
} finally {
|
||||
$this->currentInternalHostnameLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.project.application.internal-access');
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,10 @@ class Previews extends Component
|
||||
$this->previewFqdns[$previewKey] = $fqdn;
|
||||
|
||||
if (! validateDNSEntry($fqdn, $this->application->destination->server)) {
|
||||
$this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$fqdn->{$this->application->destination->server->ip}<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.");
|
||||
$server = $this->application->destination->server;
|
||||
$target = serverDnsTargetIp($server) ?? $server->ip;
|
||||
$guidance = dnsMismatchGuidanceMessage($target, $target);
|
||||
$this->dispatch('error', 'Validating DNS failed.', "{$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.");
|
||||
$success = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,13 +39,7 @@ class PreviewsCompose extends Component
|
||||
]);
|
||||
|
||||
$this->domain = ValidationPatterns::normalizeApplicationDomains($this->domain);
|
||||
|
||||
$docker_compose_domains = data_get($this->preview, 'docker_compose_domains');
|
||||
$docker_compose_domains = json_decode($docker_compose_domains, true) ?: [];
|
||||
$docker_compose_domains[$this->serviceName] = $docker_compose_domains[$this->serviceName] ?? [];
|
||||
$docker_compose_domains[$this->serviceName]['domain'] = $this->domain;
|
||||
$this->preview->docker_compose_domains = json_encode($docker_compose_domains);
|
||||
$this->preview->save();
|
||||
$this->persistPreviewDomain($this->domain);
|
||||
$this->dispatch('update_links');
|
||||
$this->dispatch('success', 'Domain saved.');
|
||||
} catch (\Throwable $e) {
|
||||
@@ -58,12 +52,8 @@ class PreviewsCompose extends Component
|
||||
try {
|
||||
$this->authorize('update', $this->preview->application);
|
||||
|
||||
$domains = collect(json_decode($this->preview->application->docker_compose_domains, true) ?: []);
|
||||
$domain = $domains->first(function ($_, $key) {
|
||||
return $key === $this->serviceName;
|
||||
});
|
||||
|
||||
$domain_string = data_get($domain, 'domain');
|
||||
$applicationDomains = json_decode($this->preview->application->docker_compose_domains ?: '[]', true) ?: [];
|
||||
$domain_string = getComposeServiceDomainString($applicationDomains, (string) $this->serviceName);
|
||||
|
||||
// If no domain is set in the main application, generate a default domain
|
||||
if (empty($domain_string)) {
|
||||
@@ -111,14 +101,8 @@ class PreviewsCompose extends Component
|
||||
$preview_fqdn = implode(',', $preview_fqdns);
|
||||
}
|
||||
|
||||
// Save the generated domain
|
||||
$this->domain = $preview_fqdn;
|
||||
$docker_compose_domains = data_get($this->preview, 'docker_compose_domains');
|
||||
$docker_compose_domains = json_decode($docker_compose_domains, true) ?: [];
|
||||
$docker_compose_domains[$this->serviceName] = $docker_compose_domains[$this->serviceName] ?? [];
|
||||
$docker_compose_domains[$this->serviceName]['domain'] = $this->domain;
|
||||
$this->preview->docker_compose_domains = json_encode($docker_compose_domains);
|
||||
$this->preview->save();
|
||||
$this->persistPreviewDomain($this->domain);
|
||||
|
||||
$this->dispatch('update_links');
|
||||
$this->dispatch('success', 'Domain generated.');
|
||||
@@ -126,4 +110,56 @@ class PreviewsCompose extends Component
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
private function persistPreviewDomain(?string $domain): void
|
||||
{
|
||||
$docker_compose_domains = json_decode(data_get($this->preview, 'docker_compose_domains') ?: '[]', true) ?: [];
|
||||
$serviceNames = $this->previewServiceNames($docker_compose_domains);
|
||||
$storageKey = findComposeServiceName((string) $this->serviceName, $serviceNames)
|
||||
?? (string) $this->serviceName;
|
||||
|
||||
$docker_compose_domains = putComposeServiceDomain(
|
||||
$docker_compose_domains,
|
||||
$storageKey,
|
||||
$domain,
|
||||
$serviceNames,
|
||||
);
|
||||
$docker_compose_domains = rekeyComposeDomainsToServiceNames($docker_compose_domains, $serviceNames);
|
||||
|
||||
$this->serviceName = $storageKey;
|
||||
$this->preview->docker_compose_domains = json_encode($docker_compose_domains);
|
||||
$this->preview->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $previewDomains
|
||||
* @return list<string>
|
||||
*/
|
||||
private function previewServiceNames(array $previewDomains): array
|
||||
{
|
||||
$parsedServices = $this->preview->application->parse(pull_request_id: $this->preview->pull_request_id);
|
||||
$fromCompose = collect(data_get($parsedServices, 'services', []))
|
||||
->keys()
|
||||
->map(function ($serviceName) {
|
||||
return str((string) $serviceName)
|
||||
->replaceLast('-pr-'.$this->preview->pull_request_id, '')
|
||||
->toString();
|
||||
})
|
||||
->all();
|
||||
|
||||
$domainKeys = collect(array_keys($previewDomains))
|
||||
->merge(array_keys(json_decode($this->preview->application->docker_compose_domains ?: '[]', true) ?: []))
|
||||
->map(fn ($name) => (string) $name);
|
||||
$unmapped = $domainKeys
|
||||
->reject(fn (string $key) => findComposeServiceName($key, $fromCompose) !== null)
|
||||
->all();
|
||||
|
||||
return collect($fromCompose)
|
||||
->merge(preferredComposeServiceNamesFromDomainKeys(
|
||||
$fromCompose === [] ? $domainKeys->all() : $unmapped
|
||||
))
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
@@ -96,9 +96,19 @@ class Source extends Component
|
||||
|
||||
private function getSources()
|
||||
{
|
||||
// filter the current source out
|
||||
$this->sources = currentTeam()->sources()->whereNotNull('app_id')->reject(function ($source) {
|
||||
return $source->id === $this->application->source_id;
|
||||
$this->sources = currentTeam()->sources()->filter(function ($source) {
|
||||
if ($source->id === $this->application->source_id
|
||||
&& $source->getMorphClass() === $this->application->source_type) {
|
||||
return false;
|
||||
}
|
||||
if ($source instanceof GithubApp) {
|
||||
return ! is_null($source->app_id);
|
||||
}
|
||||
if ($source instanceof GitlabApp) {
|
||||
return $source->isConnected();
|
||||
}
|
||||
|
||||
return true;
|
||||
})->sortBy('name');
|
||||
}
|
||||
|
||||
@@ -137,7 +147,6 @@ class Source extends Component
|
||||
|
||||
public function changeSource($sourceId, $sourceType)
|
||||
{
|
||||
|
||||
try {
|
||||
$this->authorize('update', $this->application);
|
||||
$allowedSourceTypes = [GithubApp::class, GitlabApp::class];
|
||||
@@ -150,16 +159,24 @@ class Source extends Component
|
||||
$this->dispatch('configurationChanged');
|
||||
|
||||
['repository' => $customRepository] = $this->application->customRepository();
|
||||
$repository = githubApi($this->application->source, "repos/{$customRepository}");
|
||||
$data = data_get($repository, 'data');
|
||||
$repository_project_id = data_get($data, 'id');
|
||||
if (isset($repository_project_id)) {
|
||||
if ($this->application->repository_project_id !== $repository_project_id) {
|
||||
$this->application->repository_project_id = $repository_project_id;
|
||||
$this->application->save();
|
||||
$repository_project_id = null;
|
||||
|
||||
if ($sourceType === GithubApp::class) {
|
||||
$repository = githubApi($source, "repos/{$customRepository}");
|
||||
$repository_project_id = data_get($repository, 'data.id');
|
||||
} elseif ($sourceType === GitlabApp::class) {
|
||||
if ($source->isConnected()) {
|
||||
$encoded = urlencode($customRepository);
|
||||
$project = gitlabApi($source, "/projects/{$encoded}");
|
||||
$repository_project_id = data_get($project, 'data.id');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($repository_project_id) && $this->application->repository_project_id !== $repository_project_id) {
|
||||
$this->application->repository_project_id = $repository_project_id;
|
||||
$this->application->save();
|
||||
}
|
||||
|
||||
$this->application->refresh();
|
||||
$this->getSources();
|
||||
$this->dispatch('success', 'Source updated!');
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class Status extends Component
|
||||
{
|
||||
public Application $application;
|
||||
|
||||
public function getListeners(): array
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
|
||||
return [
|
||||
"echo-private:team.{$teamId},ServiceStatusChanged" => 'refreshStatus',
|
||||
"echo-private:team.{$teamId},ServiceChecked" => 'refreshStatus',
|
||||
];
|
||||
}
|
||||
|
||||
public function refreshStatus(): void
|
||||
{
|
||||
$this->application->refresh();
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.project.application.status');
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user