diff --git a/app/Livewire/Project/Application/Deployment/Index.php b/app/Livewire/Project/Application/Deployment/Index.php index 917ff8de3..cb36a8f61 100644 --- a/app/Livewire/Project/Application/Deployment/Index.php +++ b/app/Livewire/Project/Application/Deployment/Index.php @@ -42,6 +42,10 @@ class Index extends Component public array $sourceFilterOptions = []; + public bool $embedded = false; + + public ?string $selectedDeploymentUuid = null; + protected $queryString = ['pull_request_id']; public function getListeners() @@ -79,6 +83,9 @@ class Index extends Component } $this->application = $application; + if ($this->embedded) { + $this->defaultTake = 3; + } $this->loadPullRequestOptions(); $this->loadDeploymentFilterOptions(); ['deployments' => $deployments, 'count' => $count] = $application->deployments( @@ -90,7 +97,11 @@ class Index extends Component ); $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(); } diff --git a/app/Livewire/Project/Database/Backup/Index.php b/app/Livewire/Project/Database/Backup/Index.php index 71380754f..a8b5b8e5d 100644 --- a/app/Livewire/Project/Database/Backup/Index.php +++ b/app/Livewire/Project/Database/Backup/Index.php @@ -29,7 +29,9 @@ class Index extends Component 'database_uuid' => $database->uuid, ]); } - $this->database = $database; + $this->database = $database->load([ + 'scheduledBackups' => fn ($query) => $query->withCount('executions'), + ]); } public function render() diff --git a/app/Livewire/Project/Shared/ConfigurationChecker.php b/app/Livewire/Project/Shared/ConfigurationChecker.php index 2d8b3188f..d4b939975 100644 --- a/app/Livewire/Project/Shared/ConfigurationChecker.php +++ b/app/Livewire/Project/Shared/ConfigurationChecker.php @@ -76,20 +76,10 @@ class ConfigurationChecker extends Component public function configurationChanged(): void { - // Banner only needs a lightweight summary in the Livewire snapshot. - $this->loadConfigurationState(includeChanges: false); + $this->loadConfigurationState(); } - public function refreshConfigurationChanges(): void - { - // Full change list is only needed when the user opens "View changes". - $this->loadConfigurationState(includeChanges: true); - } - - /** - * @param bool $includeChanges When false, only summary keys are stored (smaller HTML/snapshots). - */ - private function loadConfigurationState(bool $includeChanges = false): void + private function loadConfigurationState(): void { $this->resource->refresh(); @@ -99,15 +89,6 @@ class ConfigurationChecker extends Component $array = $diff->toArray(); - if (! $includeChanges) { - $this->configurationDiff = [ - 'count' => data_get($array, 'count', 0), - 'requires_build' => (bool) data_get($array, 'requires_build', false), - ]; - - return; - } - // Fail closed: only owners/admins may see unlocked env values. $redactEnvironment = ! (bool) auth()->user()?->isAdmin(); $array['changes'] = $this->redactEnvironmentChanges($array['changes'] ?? [], $redactEnvironment); diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index ba6a6e03d..7a9928a4b 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -55,11 +55,19 @@ class ResourceOperations extends Component $this->cloneVolumeData = $value; } - public function cloneTo($destination_uuid) + public function cloneTo($destination_uuid, $environment_id = null) { try { $this->authorize('update', $this->resource); + $new_environment = $this->resource->environment; + if ($environment_id !== null) { + $new_environment = Environment::ownedByCurrentTeam()->find($environment_id); + if (! $new_environment) { + return $this->addError('environment_id', 'Environment not found.'); + } + } + $new_destination = find_resource_destination_for_current_team($destination_uuid); if (! $new_destination) { return $this->addError('destination_id', 'Destination not found.'); @@ -71,11 +79,14 @@ class ResourceOperations extends Component } if ($this->resource->getMorphClass() === Application::class) { - $new_resource = clone_application($this->resource, $new_destination, ['uuid' => $uuid], $this->cloneVolumeData); + $new_resource = clone_application($this->resource, $new_destination, [ + 'uuid' => $uuid, + 'environment_id' => $new_environment->id, + ], $this->cloneVolumeData); $route = route('project.application.configuration', [ - 'project_uuid' => $this->projectUuid, - 'environment_uuid' => $this->environmentUuid, + 'project_uuid' => $new_environment->project->uuid, + 'environment_uuid' => $new_environment->uuid, 'application_uuid' => $new_resource->uuid, ]).'#resource-operations'; @@ -100,6 +111,7 @@ class ResourceOperations extends Component 'name' => $this->resource->name.'-clone-'.$uuid, 'status' => 'exited', 'started_at' => null, + 'environment_id' => $new_environment->id, 'destination_id' => $new_destination->id, 'destination_type' => $new_destination->getMorphClass(), ]); @@ -211,8 +223,8 @@ class ResourceOperations extends Component } $route = route('project.database.configuration', [ - 'project_uuid' => $this->projectUuid, - 'environment_uuid' => $this->environmentUuid, + 'project_uuid' => $new_environment->project->uuid, + 'environment_uuid' => $new_environment->uuid, 'database_uuid' => $new_resource->uuid, ]).'#resource-operations'; @@ -226,6 +238,7 @@ class ResourceOperations extends Component ])->fill([ 'uuid' => $uuid, 'name' => $this->resource->name.'-clone-'.$uuid, + 'environment_id' => $new_environment->id, 'destination_id' => $new_destination->id, 'destination_type' => $new_destination->getMorphClass(), 'server_id' => $new_destination->server_id, @@ -354,8 +367,8 @@ class ResourceOperations extends Component $new_resource->parse(); $route = route('project.service.configuration', [ - 'project_uuid' => $this->projectUuid, - 'environment_uuid' => $this->environmentUuid, + 'project_uuid' => $new_environment->project->uuid, + 'environment_uuid' => $new_environment->uuid, 'service_uuid' => $new_resource->uuid, ]).'#resource-operations'; diff --git a/app/Livewire/Project/Shared/Storages/All.php b/app/Livewire/Project/Shared/Storages/All.php index 6ef0f4bf1..54175d21a 100644 --- a/app/Livewire/Project/Shared/Storages/All.php +++ b/app/Livewire/Project/Shared/Storages/All.php @@ -36,6 +36,8 @@ class All extends Component public bool $showActionsColumn = false; + public bool $showBackupAction = false; + public bool $isComposeOrService = false; public bool $canUpdate = false; @@ -47,7 +49,8 @@ class All extends Component $this->canUpdate = (bool) auth()->user()?->can('update', $this->resource); $this->supportsPreviewSuffix = $this->resource instanceof Application && $this->resource->git_based(); - $this->showActionsColumn = $this->resource instanceof Application + $this->showActionsColumn = $this->canUpdate; + $this->showBackupAction = $this->resource instanceof Application || $this->resource instanceof ServiceApplication || $this->resource instanceof ServiceDatabase; $this->isComposeOrService = $this->resource->type() === 'service' @@ -175,7 +178,7 @@ class All extends Component { $this->volumeBackupMeta = []; - if (! $this->showActionsColumn) { + if (! $this->showBackupAction) { return; } diff --git a/app/View/Components/Forms/Button.php b/app/View/Components/Forms/Button.php index f03b36f0e..3e1600127 100644 --- a/app/View/Components/Forms/Button.php +++ b/app/View/Components/Forms/Button.php @@ -22,6 +22,7 @@ class Button extends Component public ?string $canGate = null, public mixed $canResource = null, public bool $autoDisable = true, + public bool $isHighlighted = false, public ?string $tooltip = null, ) { // Handle authorization-based disabling diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index a6e296589..3d0c80a5d 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -1340,7 +1340,8 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int if ($isPullRequest) { $labelNetwork = "{$resource->destination->network}-{$pullRequestId}"; } - $composeRedirect = data_get($domains, "$changedServiceName.redirect"); + $domainServiceName = findComposeServiceName((string) $serviceName, $domains->keys()); + $composeRedirect = data_get($domains->get($domainServiceName), 'redirect'); $redirectDirection = in_array($composeRedirect, ['www', 'non-www', 'both'], true) ? $composeRedirect : 'both'; diff --git a/public/svgs/authentik.svg b/public/svgs/authentik.svg new file mode 100644 index 000000000..c1b3eb863 --- /dev/null +++ b/public/svgs/authentik.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/azure.svg b/public/svgs/azure.svg new file mode 100644 index 000000000..69a264e21 --- /dev/null +++ b/public/svgs/azure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/bitbucket.svg b/public/svgs/bitbucket.svg new file mode 100644 index 000000000..6a9779075 --- /dev/null +++ b/public/svgs/bitbucket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/clerk.svg b/public/svgs/clerk.svg new file mode 100644 index 000000000..b30e97ef6 --- /dev/null +++ b/public/svgs/clerk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/github.svg b/public/svgs/github.svg index 270db203b..538ec5bf2 100644 --- a/public/svgs/github.svg +++ b/public/svgs/github.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/public/svgs/gitlab.svg b/public/svgs/gitlab.svg index 1c7cb0719..ab1286a21 100644 --- a/public/svgs/gitlab.svg +++ b/public/svgs/gitlab.svg @@ -1 +1 @@ - + diff --git a/public/svgs/google.svg b/public/svgs/google.svg new file mode 100644 index 000000000..2eaf91554 --- /dev/null +++ b/public/svgs/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/infomaniak.svg b/public/svgs/infomaniak.svg new file mode 100644 index 000000000..a1adc6504 --- /dev/null +++ b/public/svgs/infomaniak.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/zitadel.svg b/public/svgs/zitadel.svg new file mode 100644 index 000000000..82e4703a7 --- /dev/null +++ b/public/svgs/zitadel.svg @@ -0,0 +1,76 @@ + + + diff --git a/resources/css/app.css b/resources/css/app.css index b7b0943ab..c8fe49a65 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -433,6 +433,55 @@ tr td:first-child { opacity 220ms ease; } +.application-console-shell[data-console-theme="system"], +.terminal-fullscreen-shell[data-console-theme="system"] { + --console-theme-background: #fff; + --console-theme-opacity: 1; +} + +html:not(.dark) .application-console-shell[data-console-theme="system"] .application-console-header { + color: #52525b; + border-color: rgb(0 0 0 / 0.1); +} + +html:not(.dark) .application-console-shell[data-console-theme="system"] .application-console-header [class*="text-white"] { + color: #52525b !important; +} + +html.dark .application-console-shell[data-console-theme="system"], +html.dark .terminal-fullscreen-shell[data-console-theme="system"] { + --console-theme-background: #121214; +} + +.console-theme-selector { + scrollbar-color: rgb(161 161 170) transparent; + scrollbar-width: thin; +} + +.console-theme-selector::-webkit-scrollbar { + width: 8px; +} + +.console-theme-selector::-webkit-scrollbar-thumb { + border: 2px solid transparent; + border-radius: 9999px; + background: rgb(161 161 170); + background-clip: padding-box; +} + +.console-theme-selector::-webkit-scrollbar-track { + background: transparent; +} + +html.dark .console-theme-selector { + scrollbar-color: rgb(255 255 255 / 0.28) transparent; +} + +html.dark .console-theme-selector::-webkit-scrollbar-thumb { + background: rgb(255 255 255 / 0.28); + background-clip: padding-box; +} + .application-console-shell[data-console-theme="shadows-midnight"], .terminal-fullscreen-shell[data-console-theme="shadows-midnight"] { --console-theme-background: linear-gradient(135deg, #2a3b4c, rgb(42 59 76 / 0.4)); @@ -1049,6 +1098,18 @@ body.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-too overscroll-behavior: contain; scrollbar-width: thin; } + + /* Resource pages have a second fixed header row below the main header. */ + .application-settings-workspace > .application-settings-navigation { + top: 4rem; + max-height: calc(100dvh - 5rem); + } + + .application-settings-navigation.is-flush { + position: static; + max-height: none; + overflow: visible; + } } /* Field labels */ @@ -1460,6 +1521,11 @@ body.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-too background: var(--coollabs-fill); } +.data-table-row-active { + background: var(--coollabs-fill); + color: var(--coollabs-fg); +} + .listbox-option-disabled { cursor: not-allowed; opacity: 0.4; @@ -1542,9 +1608,20 @@ input[type="search"]::-webkit-search-results-decoration { padding: 0; } -/* Empty states sit edge-to-edge in settings cards — no outer inset/padding. */ -.application-settings-section-body:has(> .empty-state:only-child) { - padding: 0; +/* Empty states inside cards keep the same inset as other card content. */ +.application-settings-section-body:has(> .empty-state:only-child), +.application-settings-section-body.is-flush:has(> .empty-state:only-child) { + padding: 1rem; +} + +.empty-state { + background: var(--coollabs-base); +} + +.loading-state-card { + border: 1px dashed var(--coollabs-line); + border-radius: 12px; + background: var(--coollabs-base); } .application-settings-section-body.is-flush .empty-state, @@ -1554,8 +1631,6 @@ input[type="search"]::-webkit-search-results-decoration { width: 100%; max-width: none; box-sizing: border-box; - /* Match the layer-card body corners when the empty fills the body. */ - border-radius: 8px; } /* Centered process dialog (startup / activity logs) — full-height log body. diff --git a/resources/js/terminal.js b/resources/js/terminal.js index f783340f5..25faa77c0 100644 --- a/resources/js/terminal.js +++ b/resources/js/terminal.js @@ -42,7 +42,28 @@ function createApplicationTerminalTheme(accent, colors = {}) { }; } +function createSystemTerminalTheme() { + if (document.documentElement.classList.contains('dark')) { + return createApplicationTerminalTheme('#8C8E9C'); + } + + return createApplicationTerminalTheme('#52525b', { + black: '#18181b', + red: '#dc2626', + green: '#15803d', + yellow: '#a16207', + blue: '#2563eb', + magenta: '#9333ea', + cyan: '#0e7490', + white: '#52525b', + brightBlack: '#71717a', + brightWhite: '#18181b', + foreground: '#18181b', + }); +} + const applicationTerminalThemes = { + 'system': createSystemTerminalTheme(), 'shadows-midnight': createApplicationTerminalTheme('#6d7a7c', { blue: '#7392ad', cyan: '#7fa3a6', @@ -155,14 +176,26 @@ export function initializeTerminalComponent() { scrollLockY: 0, scrollLockStyles: null, preventPageScrollHandler: null, + themeObserver: null, terminalSessionStartedAt: null, terminalSessionRemainingSeconds: null, terminalSessionCountdownInterval: null, selectedTheme: applicationTerminalThemes[localStorage.getItem('coolify-console-theme')] ? localStorage.getItem('coolify-console-theme') - : 'shadows-cosmic-purple', + : 'system', init() { + this.themeObserver = new MutationObserver(() => { + if (this.selectedTheme === 'system') { + applicationTerminalThemes.system = createSystemTerminalTheme(); + this.setTerminalTheme('system'); + } + }); + this.themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class', 'data-theme'], + }); + // Recover if a previous portal build left the terminal on
. this.$nextTick(() => this.salvageStrayFullscreenNodes()); @@ -343,6 +376,10 @@ export function initializeTerminalComponent() { return; } + if (themeName === 'system') { + applicationTerminalThemes.system = createSystemTerminalTheme(); + } + this.selectedTheme = themeName; localStorage.setItem('coolify-console-theme', themeName); @@ -413,7 +450,7 @@ export function initializeTerminalComponent() { disableStdin: false, scrollback: 5000, theme: isApplicationConsole - ? applicationTerminalThemes[this.selectedTheme] ?? applicationTerminalThemes['shadows-cosmic-purple'] + ? applicationTerminalThemes[this.selectedTheme] ?? applicationTerminalThemes.system : undefined }); this.fitAddon = new FitAddon(); @@ -747,6 +784,10 @@ export function initializeTerminalComponent() { }); }, + destroy() { + this.themeObserver?.disconnect(); + }, + sendTerminalInput(data) { if (!this.term || !this.terminalActive) { diff --git a/resources/views/components/application/configuration-sidebar.blade.php b/resources/views/components/application/configuration-sidebar.blade.php new file mode 100644 index 000000000..52a4a9b7a --- /dev/null +++ b/resources/views/components/application/configuration-sidebar.blade.php @@ -0,0 +1,286 @@ +@props(['application', 'currentRoute', 'flush' => false]) + +@php + $applicationRouteParameters = [ + 'project_uuid' => $application->environment->project->uuid, + 'environment_uuid' => $application->environment->uuid, + 'application_uuid' => $application->uuid, + ]; + + $configurationMenuItems = [ + [ + 'label' => 'General', + 'route' => 'project.application.configuration', + 'active' => $currentRoute === 'project.application.configuration', + ], + [ + 'label' => 'Domains', + 'route' => 'project.application.domains', + 'active' => $currentRoute === 'project.application.domains', + ], + [ + 'label' => 'Advanced', + 'route' => 'project.application.advanced', + 'active' => $currentRoute === 'project.application.advanced', + ], + [ + 'label' => 'Swarm', + 'route' => 'project.application.swarm', + 'active' => $currentRoute === 'project.application.swarm', + 'visible' => $application->destination->server->isSwarm(), + ], + [ + 'label' => 'Environment Variables', + 'route' => 'project.application.environment-variables', + 'active' => $currentRoute === 'project.application.environment-variables', + ], + [ + 'label' => 'Persistent Storage', + 'route' => 'project.application.persistent-storage', + 'active' => $currentRoute === 'project.application.persistent-storage', + ], + [ + 'label' => 'Backups', + 'route' => 'project.application.backup.index', + 'active' => str($currentRoute)->startsWith('project.application.backup'), + ], + [ + 'label' => 'Terminal', + 'route' => 'project.application.command', + 'active' => $currentRoute === 'project.application.command', + 'navigate' => false, + 'visible' => ! $application->destination->server->isSwarm() && auth()->user()?->can('canAccessTerminal'), + ], + [ + 'label' => 'Deployment', + 'route' => 'project.application.deployment.index', + 'active' => str($currentRoute)->startsWith('project.application.deployment'), + ], + [ + 'label' => 'Runtime', + 'route' => 'project.application.logs', + 'active' => $currentRoute === 'project.application.logs', + ], + [ + 'label' => 'Git Source', + 'route' => 'project.application.source', + 'active' => $currentRoute === 'project.application.source', + 'visible' => $application->git_based(), + ], + [ + 'label' => 'Servers', + 'route' => 'project.application.servers', + 'active' => $currentRoute === 'project.application.servers', + 'badge' => true, + ], + [ + 'label' => 'Scheduled Tasks', + 'route' => 'project.application.scheduled-tasks.show', + 'active' => str($currentRoute)->startsWith('project.application.scheduled-tasks'), + ], + [ + 'label' => 'Webhooks', + 'route' => 'project.application.webhooks', + 'active' => $currentRoute === 'project.application.webhooks', + ], + [ + 'label' => 'Preview Deployments', + 'route' => 'project.application.preview-deployments', + 'active' => $currentRoute === 'project.application.preview-deployments', + 'visible' => $application->git_based() || $application->build_pack === 'dockerimage', + ], + [ + 'label' => 'Healthcheck', + 'route' => 'project.application.healthcheck', + 'active' => $currentRoute === 'project.application.healthcheck', + 'visible' => $application->build_pack !== 'dockercompose', + ], + [ + 'label' => 'Rollback', + 'route' => 'project.application.rollback', + 'active' => $currentRoute === 'project.application.rollback', + ], + [ + 'label' => 'Resource Limits', + 'route' => 'project.application.resource-limits', + 'active' => $currentRoute === 'project.application.resource-limits', + ], + [ + 'label' => 'Resource Operations', + 'route' => 'project.application.resource-operations', + 'active' => $currentRoute === 'project.application.resource-operations', + ], + [ + 'label' => 'Metrics', + 'route' => 'project.application.metrics', + 'active' => $currentRoute === 'project.application.metrics', + ], + [ + 'label' => 'Tags', + 'route' => 'project.application.tags', + 'active' => $currentRoute === 'project.application.tags', + ], + [ + 'label' => 'Danger Zone', + 'route' => 'project.application.danger', + 'active' => $currentRoute === 'project.application.danger', + ], + ]; + + $configurationMenuItems = array_values(array_filter( + $configurationMenuItems, + fn (array $item): bool => $item['visible'] ?? true, + )); + + // Icons follow the main sidebar's reicon set + $menuIcons = [ + 'General' => 'settings', + 'Domains' => 'globe', + 'Advanced' => 'grid', + 'Swarm' => 'destinations', + 'Environment Variables' => 'variables', + 'Persistent Storage' => 'storages', + 'Backups' => 'database', + 'Terminal' => 'browser-terminal', + 'Deployment' => 'time-back', + 'Runtime' => 'unordered-list', + 'Git Source' => 'sources', + 'Servers' => 'servers', + 'Scheduled Tasks' => 'calendar', + 'Webhooks' => 'notifications', + 'Preview Deployments' => 'eye', + 'Healthcheck' => 'feedback', + 'Rollback' => 'time-back', + 'Resource Limits' => 'cpu', + 'Resource Operations' => 'server-update', + 'Metrics' => 'graph', + 'Tags' => 'tags', + 'Danger Zone' => 'shield-alert', + ]; + + // Discord-style groups for the settings sidebar + $menuGroups = [ + 'Settings' => ['General', 'Domains', 'Advanced', 'Swarm', 'Environment Variables', 'Persistent Storage', 'Backups'], + 'Build & deploy' => ['Git Source', 'Servers', 'Healthcheck'], + 'Automation' => ['Scheduled Tasks', 'Webhooks', 'Preview Deployments'], + 'Logs' => ['Deployment', 'Runtime'], + 'Operations' => ['Terminal', 'Rollback', 'Resource Limits', 'Resource Operations', 'Metrics', 'Tags', 'Danger Zone'], + ]; + $groupedMenuItems = collect($menuGroups) + ->map(fn (array $labels) => collect($configurationMenuItems)->whereIn('label', $labels)->values()) + ->filter(fn ($items) => $items->isNotEmpty()); + + // In-page sections (cards) shown as sub-items under the active page + $isComposeApp = $application->build_pack === 'dockercompose'; + $pageSections = [ + 'project.application.configuration' => array_values(array_filter([ + ['id' => 'application-details-section', 'label' => 'Application details'], + ['id' => 'public-access-section', 'label' => 'Public access'], + ['id' => 'build-pipeline-section', 'label' => 'Build pipeline'], + $isComposeApp ? null : ['id' => 'container-image-section', 'label' => 'Container image'], + $isComposeApp ? null : ['id' => 'networking-section', 'label' => 'Networking'], + $isComposeApp ? null : ['id' => 'runtime-section', 'label' => 'Runtime'], + $isComposeApp ? null : ['id' => 'security-section', 'label' => 'Security'], + ['id' => 'deployment-lifecycle-section', 'label' => 'Deployment lifecycle'], + $isComposeApp ? null : ['id' => 'container-labels-section', 'label' => 'Container labels'], + ])), + 'project.application.advanced' => array_values(array_filter([ + ['id' => 'advanced-build-section', 'label' => 'Build'], + ['id' => 'advanced-container-section', 'label' => 'Container'], + $application->git_based() ? ['id' => 'advanced-deployment-section', 'label' => 'Deployment'] : null, + $application->git_based() ? ['id' => 'advanced-git-section', 'label' => 'Git'] : null, + $isComposeApp ? ['id' => 'advanced-compose-section', 'label' => 'Docker compose'] : null, + ['id' => 'advanced-proxy-section', 'label' => 'Proxy'], + ['id' => 'advanced-operations-section', 'label' => 'Operations'], + ['id' => 'advanced-logs-section', 'label' => 'Logs'], + $isComposeApp ? null : ['id' => 'advanced-gpu-section', 'label' => 'GPU'], + ])), + 'project.application.webhooks' => [ + ['id' => 'deploy-webhook-section', 'label' => 'Deploy webhook'], + ['id' => 'manual-git-webhooks-section', 'label' => 'Manual Git webhooks'], + ], + 'project.application.preview-deployments' => array_values(array_filter([ + ['id' => 'preview-template-section', 'label' => 'URL template'], + $application->is_github_based() + ? ['id' => 'preview-pull-requests-section', 'label' => 'Pull requests'] + : null, + $application->build_pack === 'dockerimage' + ? ['id' => 'manual-preview-section', 'label' => 'Manual preview'] + : null, + ['id' => 'preview-deployments-section', 'label' => 'Deployments'], + ])), + 'project.application.healthcheck' => [ + ['id' => 'healthcheck-configuration-section', 'label' => 'Configuration'], + [ + 'id' => $application->health_check_type === 'cmd' + ? 'healthcheck-command-section' + : 'healthcheck-request-section', + 'label' => $application->health_check_type === 'cmd' ? 'Command' : 'HTTP request', + ], + ['id' => 'healthcheck-timing-section', 'label' => 'Timing and retries'], + ], + 'project.application.rollback' => [ + ['id' => 'rollback-retention-section', 'label' => 'Image retention'], + ['id' => 'rollback-images-section', 'label' => 'Available images'], + ], + 'project.application.resource-limits' => [ + ['id' => 'cpu-limits-section', 'label' => 'CPU'], + ['id' => 'memory-limits-section', 'label' => 'Memory'], + ], + 'project.application.resource-operations' => [ + ['id' => 'clone-destination-section', 'label' => 'Clone destination'], + ['id' => 'clone-environment-section', 'label' => 'Clone environment'], + ['id' => 'move-resource-section', 'label' => 'Move resource'], + ], + ]; + @endphp + + diff --git a/resources/views/components/application/settings-section.blade.php b/resources/views/components/application/settings-section.blade.php index 241128728..ad6e3f78b 100644 --- a/resources/views/components/application/settings-section.blade.php +++ b/resources/views/components/application/settings-section.blade.php @@ -6,17 +6,12 @@ ])- {{ $description }} + {!! $description ?? $helper !!}
@endif