mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-18 02:14:32 +00:00
feat(ui): Shadow UI redesign, domain management, and DNS autoconfigure (#11119)
Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com>
This commit is contained in:
1 parent
0b843bb07c
commit
8ae56587d5
497 files changed
+44045
-22630
No files matched your search
@@ -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,18 @@ class Index extends Component
|
||||
|
||||
public ?string $pull_request_id = null;
|
||||
|
||||
public array $pullRequestOptions = [];
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public string $deploymentFilter = 'all';
|
||||
|
||||
public string $deploymentSort = 'newest';
|
||||
|
||||
public array $statusFilterOptions = [];
|
||||
|
||||
public array $sourceFilterOptions = [];
|
||||
|
||||
protected $queryString = ['pull_request_id'];
|
||||
|
||||
public function getListeners()
|
||||
@@ -64,8 +78,16 @@ class Index extends Component
|
||||
}
|
||||
}
|
||||
|
||||
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, $this->defaultTake, $this->pull_request_id);
|
||||
$this->application = $application;
|
||||
$this->loadPullRequestOptions();
|
||||
$this->loadDeploymentFilterOptions();
|
||||
['deployments' => $deployments, 'count' => $count] = $application->deployments(
|
||||
search: $this->search,
|
||||
filter: $this->deploymentFilter,
|
||||
sort: $this->deploymentSort,
|
||||
take: $this->defaultTake,
|
||||
pullRequestId: $this->pull_request_id,
|
||||
);
|
||||
$this->deployments = $deployments;
|
||||
$this->deployments_count = $count;
|
||||
$this->current_url = url()->current();
|
||||
@@ -114,14 +136,75 @@ class Index extends Component
|
||||
$this->loadDeployments();
|
||||
}
|
||||
|
||||
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,
|
||||
filter: $this->deploymentFilter,
|
||||
sort: $this->deploymentSort,
|
||||
);
|
||||
$this->deployments = $deployments;
|
||||
$this->deployments_count = $count;
|
||||
$this->showMore();
|
||||
}
|
||||
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
$this->resetPaginationAndLoad();
|
||||
}
|
||||
|
||||
public function setDeploymentFilter(string $filter): void
|
||||
{
|
||||
$validFilters = collect($this->statusFilterOptions)
|
||||
->concat($this->sourceFilterOptions)
|
||||
->pluck('value')
|
||||
->push('all');
|
||||
|
||||
if (! $validFilters->contains($filter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->deploymentFilter = $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->deploymentFilter = 'all';
|
||||
$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 +222,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->deploymentFilter = 'all';
|
||||
$this->resetPaginationAndLoad();
|
||||
}
|
||||
|
||||
public function clearFilter()
|
||||
{
|
||||
$this->pull_request_id = null;
|
||||
$this->skip = 0;
|
||||
$this->showPrev = false;
|
||||
$this->updateCurrentPage();
|
||||
$this->loadDeployments();
|
||||
$this->deploymentFilter = 'all';
|
||||
$this->resetPaginationAndLoad();
|
||||
}
|
||||
|
||||
private function updateCurrentPage()
|
||||
@@ -160,6 +238,95 @@ 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();
|
||||
}
|
||||
|
||||
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
@@ -94,6 +94,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;
|
||||
@@ -437,6 +440,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;
|
||||
@@ -444,6 +448,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 {
|
||||
@@ -680,7 +691,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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -722,7 +736,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;
|
||||
}
|
||||
@@ -847,7 +864,10 @@ class General extends Component
|
||||
$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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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->activeRouteName = request()->route()?->getName() ?? '';
|
||||
$this->parameters = [
|
||||
'project_uuid' => $this->application->project()->uuid,
|
||||
'environment_uuid' => $this->application->environment->uuid,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user