mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 10:18:20 +00:00
Merge remote-tracking branch 'origin/next' into jean/port-exposes-improvement
This commit is contained in:
commit
07f381b88c
766 files changed
+43286
-12686
No files matched your search
@@ -5,19 +5,7 @@
|
||||
</div>
|
||||
<div>Advanced configuration for your application.</div>
|
||||
<div class="flex flex-col gap-1 pt-4">
|
||||
<h3>General</h3>
|
||||
@if ($application->git_based())
|
||||
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
|
||||
id="isAutoDeployEnabled" label="Auto Deploy" canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox
|
||||
helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments."
|
||||
instantSave id="isPreviewDeploymentsEnabled" label="Preview Deployments" canGate="update"
|
||||
:canResource="$application" />
|
||||
<x-forms.checkbox
|
||||
helper="When enabled, anyone can trigger PR deployments. When disabled, only repository members, collaborators, and contributors can trigger PR deployments."
|
||||
instantSave id="isPrDeploymentsPublicEnabled" label="Allow Public PR Deployments" canGate="update"
|
||||
:canResource="$application" :disabled="!$isPreviewDeploymentsEnabled" />
|
||||
@endif
|
||||
<h3>Build</h3>
|
||||
<x-forms.checkbox helper="Disable Docker build cache on every deployment." instantSave
|
||||
id="disableBuildCache" label="Disable Build Cache" canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox
|
||||
@@ -29,6 +17,55 @@
|
||||
instantSave id="includeSourceCommitInBuild" label="Include Source Commit in Build" canGate="update"
|
||||
:canResource="$application" />
|
||||
|
||||
<h3 class="pt-4">Container</h3>
|
||||
<x-forms.checkbox
|
||||
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
instantSave id="isConsistentContainerNameEnabled" label="Consistent Container Names" canGate="update"
|
||||
:canResource="$application" />
|
||||
@if ($isConsistentContainerNameEnabled === false)
|
||||
<form class="flex items-end gap-2 " wire:submit.prevent='saveCustomName'>
|
||||
<x-forms.input
|
||||
helper="You can add a custom name for your container.<br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
instantSave id="customInternalName" label="Custom Container Name" canGate="update"
|
||||
:canResource="$application" />
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if ($application->git_based())
|
||||
<h3 class="pt-4">Deployment</h3>
|
||||
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
|
||||
id="isAutoDeployEnabled" label="Auto Deploy" canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox
|
||||
helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments."
|
||||
instantSave id="isPreviewDeploymentsEnabled" label="Preview Deployments" canGate="update"
|
||||
:canResource="$application" />
|
||||
<x-forms.checkbox
|
||||
helper="When enabled, anyone can trigger PR deployments. When disabled, fork PRs are blocked and only repository owners, members, and collaborators can trigger PR deployments."
|
||||
instantSave id="isPrDeploymentsPublicEnabled" label="Allow Public PR Deployments" canGate="update"
|
||||
:canResource="$application" :disabled="!$isPreviewDeploymentsEnabled" />
|
||||
|
||||
<h3 class="pt-4">Git</h3>
|
||||
<x-forms.checkbox instantSave id="isGitSubmodulesEnabled" label="Submodules"
|
||||
helper="Allow Git Submodules during build process." canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox instantSave id="isGitLfsEnabled" label="LFS"
|
||||
helper="Allow Git LFS during build process." canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox instantSave id="isGitShallowCloneEnabled" label="Shallow Clone"
|
||||
helper="Use shallow cloning (--depth=1) to speed up deployments by only fetching the latest commit history. This reduces clone time and resource usage, especially for large repositories."
|
||||
canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<h3 class="pt-4">Docker Compose</h3>
|
||||
<x-forms.checkbox instantSave id="isRawComposeDeploymentEnabled" label="Raw Compose Deployment"
|
||||
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>"
|
||||
canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox instantSave id="isConnectToDockerNetworkEnabled" label="Connect To Predefined Network"
|
||||
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>."
|
||||
canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
|
||||
<h3 class="pt-4">Proxy</h3>
|
||||
@if ($application->settings->is_container_label_readonly_enabled)
|
||||
<x-forms.checkbox
|
||||
helper="Your application will be available only on https if your domain starts with https://..."
|
||||
@@ -49,45 +86,24 @@
|
||||
helper="Readonly labels are disabled. You need to set the labels in the labels section." disabled
|
||||
instantSave id="isStripprefixEnabled" label="Strip Prefixes" canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<h3>Docker Compose</h3>
|
||||
<x-forms.checkbox instantSave id="isRawComposeDeploymentEnabled" label="Raw Compose Deployment"
|
||||
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>"
|
||||
canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
<h3 class="pt-4">Container Names</h3>
|
||||
<x-forms.checkbox
|
||||
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
instantSave id="isConsistentContainerNameEnabled" label="Consistent Container Names" canGate="update"
|
||||
:canResource="$application" />
|
||||
@if ($isConsistentContainerNameEnabled === false)
|
||||
<form class="flex items-end gap-2 " wire:submit.prevent='saveCustomName'>
|
||||
<x-forms.input
|
||||
helper="You can add a custom name for your container.<br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
instantSave id="customInternalName" label="Custom Container Name" canGate="update"
|
||||
:canResource="$application" />
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
</form>
|
||||
@endif
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<h3 class="pt-4">Network</h3>
|
||||
<x-forms.checkbox instantSave id="isConnectToDockerNetworkEnabled" label="Connect To Predefined Network"
|
||||
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>."
|
||||
canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
<h3 class="pt-4">Operations</h3>
|
||||
<form class="flex items-end gap-2" wire:submit.prevent='saveStopGracePeriod'>
|
||||
<x-forms.input
|
||||
type="number"
|
||||
id="stopGracePeriod"
|
||||
label="Stop Grace Period (seconds)"
|
||||
placeholder="{{ DEFAULT_STOP_GRACE_PERIOD_SECONDS }}"
|
||||
helper="How long to wait for graceful shutdown during rolling updates, manual stops, and restarts. Applies to all containers for this application. Default: {{ DEFAULT_STOP_GRACE_PERIOD_SECONDS }} seconds. Range: {{ MIN_STOP_GRACE_PERIOD_SECONDS }}-{{ MAX_STOP_GRACE_PERIOD_SECONDS }} seconds (1 hour)."
|
||||
min="{{ MIN_STOP_GRACE_PERIOD_SECONDS }}"
|
||||
max="{{ MAX_STOP_GRACE_PERIOD_SECONDS }}"
|
||||
canGate="update"
|
||||
:canResource="$application"
|
||||
/>
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
</form>
|
||||
<h3 class="pt-4">Logs</h3>
|
||||
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
|
||||
instantSave id="isLogDrainEnabled" label="Drain Logs" canGate="update" :canResource="$application" />
|
||||
@if ($application->git_based())
|
||||
<h3>Git</h3>
|
||||
<x-forms.checkbox instantSave id="isGitSubmodulesEnabled" label="Submodules"
|
||||
helper="Allow Git Submodules during build process." canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox instantSave id="isGitLfsEnabled" label="LFS"
|
||||
helper="Allow Git LFS during build process." canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox instantSave id="isGitShallowCloneEnabled" label="Shallow Clone"
|
||||
helper="Use shallow cloning (--depth=1) to speed up deployments by only fetching the latest commit history. This reduces clone time and resource usage, especially for large repositories."
|
||||
canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
href="{{ route('project.application.advanced', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Advanced</span></a>
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Swarm Configuration</span></a>
|
||||
href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Swarm</span>
|
||||
</a>
|
||||
@endif
|
||||
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.application.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Environment Variables</span></a>
|
||||
@@ -26,27 +27,13 @@
|
||||
@endif
|
||||
<a class="sub-menu-item flex items-center gap-2" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.application.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Servers</span>
|
||||
@if ($application->server_status == false)
|
||||
<span title="One or more servers are unreachable or misconfigured.">
|
||||
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
|
||||
</svg>
|
||||
</span>
|
||||
@elseif ($application->additional_servers()->exists() && str($application->status)->contains('degraded'))
|
||||
<span title="Application is in degraded state across multiple servers.">
|
||||
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
|
||||
</svg>
|
||||
</span>
|
||||
@endif
|
||||
<livewire:project.application.server-status-badge :application="$application" />
|
||||
</a>
|
||||
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
<a @class(['sub-menu-item', 'menu-item-active' => str($currentRoute)->startsWith('project.application.scheduled-tasks')]) {{ wireNavigate() }}
|
||||
href="{{ route('project.application.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Scheduled Tasks</span></a>
|
||||
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.application.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Webhooks</span></a>
|
||||
@if ($application->git_based())
|
||||
@if ($application->git_based() || $application->build_pack === 'dockerimage')
|
||||
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.application.preview-deployments', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Preview Deployments</span></a>
|
||||
@endif
|
||||
@@ -84,6 +71,8 @@
|
||||
<livewire:project.shared.destination :resource="$application" />
|
||||
@elseif ($currentRoute === 'project.application.scheduled-tasks.show')
|
||||
<livewire:project.shared.scheduled-task.all :resource="$application" />
|
||||
@elseif ($currentRoute === 'project.application.scheduled-tasks')
|
||||
<livewire:project.shared.scheduled-task.show />
|
||||
@elseif ($currentRoute === 'project.application.webhooks')
|
||||
<livewire:project.shared.webhooks :resource="$application" />
|
||||
@elseif ($currentRoute === 'project.application.preview-deployments')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div class="flex h-[calc(100vh-10rem)] min-h-[50rem] flex-col overflow-hidden">
|
||||
<x-slot:title>
|
||||
{{ data_get_str($application, 'name')->limit(10) }} > Deployment | Coolify
|
||||
</x-slot>
|
||||
@@ -9,6 +9,13 @@
|
||||
fullscreen: @entangle('fullscreen'),
|
||||
alwaysScroll: {{ $isKeepAliveOn ? 'true' : 'false' }},
|
||||
rafId: null,
|
||||
scrollTimeout: null,
|
||||
scrollDebounce: null,
|
||||
isScrolling: false,
|
||||
destroyed: false,
|
||||
morphUpdatedCleanup: null,
|
||||
deploymentFinishedCleanup: null,
|
||||
lastTouchY: 0,
|
||||
showTimestamps: true,
|
||||
searchQuery: '',
|
||||
matchCount: 0,
|
||||
@@ -17,17 +24,76 @@
|
||||
this.fullscreen = !this.fullscreen;
|
||||
},
|
||||
scrollToBottom() {
|
||||
const logsContainer = document.getElementById('logsContainer');
|
||||
if (this.destroyed) return;
|
||||
const logsContainer = this.$root.querySelector('#logsContainer');
|
||||
if (logsContainer) {
|
||||
this.isScrolling = true;
|
||||
logsContainer.scrollTop = logsContainer.scrollHeight;
|
||||
requestAnimationFrame(() => { this.isScrolling = false; });
|
||||
}
|
||||
},
|
||||
scheduleScroll() {
|
||||
cancelScrollLoop() {
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
if (this.scrollTimeout) {
|
||||
clearTimeout(this.scrollTimeout);
|
||||
this.scrollTimeout = null;
|
||||
}
|
||||
if (this.scrollDebounce) {
|
||||
clearTimeout(this.scrollDebounce);
|
||||
this.scrollDebounce = null;
|
||||
}
|
||||
},
|
||||
disableFollow() {
|
||||
if (!this.alwaysScroll) return;
|
||||
this.alwaysScroll = false;
|
||||
this.cancelScrollLoop();
|
||||
},
|
||||
handleWheel(event) {
|
||||
if (this.alwaysScroll && event.deltaY < 0) {
|
||||
this.disableFollow();
|
||||
}
|
||||
},
|
||||
handleTouchStart(event) {
|
||||
this.lastTouchY = event.touches[0].clientY;
|
||||
},
|
||||
handleTouchMove(event) {
|
||||
if (!this.alwaysScroll) return;
|
||||
const currentY = event.touches[0].clientY;
|
||||
if (currentY > this.lastTouchY) {
|
||||
this.disableFollow();
|
||||
}
|
||||
this.lastTouchY = currentY;
|
||||
},
|
||||
handleKeyScroll(event) {
|
||||
if (!this.alwaysScroll) return;
|
||||
const upKeys = ['ArrowUp', 'PageUp', 'Home'];
|
||||
if (upKeys.includes(event.key)) {
|
||||
this.disableFollow();
|
||||
}
|
||||
},
|
||||
handleScroll(event) {
|
||||
if (this.isScrolling || this.destroyed) return;
|
||||
const el = event.target;
|
||||
clearTimeout(this.scrollDebounce);
|
||||
this.scrollDebounce = setTimeout(() => {
|
||||
if (this.destroyed) return;
|
||||
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||
if (!this.alwaysScroll && distanceFromBottom <= 10) {
|
||||
this.alwaysScroll = true;
|
||||
this.scheduleScroll();
|
||||
}
|
||||
}, 150);
|
||||
},
|
||||
scheduleScroll() {
|
||||
if (!this.alwaysScroll || this.destroyed) return;
|
||||
this.rafId = requestAnimationFrame(() => {
|
||||
if (!this.alwaysScroll || this.destroyed) return;
|
||||
this.scrollToBottom();
|
||||
if (this.alwaysScroll) {
|
||||
setTimeout(() => this.scheduleScroll(), 250);
|
||||
if (this.alwaysScroll && !this.destroyed) {
|
||||
this.scrollTimeout = setTimeout(() => this.scheduleScroll(), 250);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -36,10 +102,7 @@
|
||||
if (this.alwaysScroll) {
|
||||
this.scheduleScroll();
|
||||
} else {
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
this.cancelScrollLoop();
|
||||
}
|
||||
},
|
||||
hasActiveLogSelection() {
|
||||
@@ -107,9 +170,9 @@
|
||||
|
||||
this.matchCount = query ? count : 0;
|
||||
},
|
||||
downloadLogs() {
|
||||
collectVisibleLogs() {
|
||||
const logs = document.getElementById('logs');
|
||||
if (!logs) return;
|
||||
if (!logs) return '';
|
||||
const visibleLines = logs.querySelectorAll('[data-log-line]:not(.hidden)');
|
||||
let content = '';
|
||||
visibleLines.forEach(line => {
|
||||
@@ -118,6 +181,17 @@
|
||||
content += text + String.fromCharCode(10);
|
||||
}
|
||||
});
|
||||
return content;
|
||||
},
|
||||
copyLogs() {
|
||||
const content = this.collectVisibleLogs();
|
||||
if (!content) return;
|
||||
navigator.clipboard.writeText(content);
|
||||
Livewire.dispatch('success', ['Logs copied to clipboard.']);
|
||||
},
|
||||
downloadLogs() {
|
||||
const content = this.collectVisibleLogs();
|
||||
if (!content) return;
|
||||
const blob = new Blob([content], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
@@ -130,10 +204,7 @@
|
||||
stopScroll() {
|
||||
this.scrollToBottom();
|
||||
this.alwaysScroll = false;
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
this.cancelScrollLoop();
|
||||
},
|
||||
init() {
|
||||
// Watch search query changes
|
||||
@@ -141,21 +212,26 @@
|
||||
this.applySearch();
|
||||
});
|
||||
|
||||
// Apply search after Livewire updates
|
||||
Livewire.hook('morph.updated', ({ el }) => {
|
||||
if (el.id === 'logs') {
|
||||
this.$nextTick(() => {
|
||||
this.applySearch();
|
||||
if (this.alwaysScroll) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Apply search after Livewire updates.
|
||||
// Livewire.hook() returns an unregister fn; keep it for destroy().
|
||||
this.morphUpdatedCleanup = Livewire.hook('morph.updated', ({ el }) => {
|
||||
if (this.destroyed) return;
|
||||
if (el.id !== 'logs' || !this.$root.contains(el)) return;
|
||||
this.$nextTick(() => {
|
||||
if (this.destroyed) return;
|
||||
this.applySearch();
|
||||
if (this.alwaysScroll) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Stop auto-scroll when deployment finishes
|
||||
Livewire.on('deploymentFinished', () => {
|
||||
// Stop auto-scroll when deployment finishes.
|
||||
// Livewire.on() returns an unregister fn; keep it for destroy().
|
||||
this.deploymentFinishedCleanup = Livewire.on('deploymentFinished', () => {
|
||||
if (this.destroyed) return;
|
||||
setTimeout(() => {
|
||||
if (this.destroyed) return;
|
||||
this.stopScroll();
|
||||
}, 500);
|
||||
});
|
||||
@@ -164,14 +240,32 @@
|
||||
if (this.alwaysScroll) {
|
||||
this.scheduleScroll();
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
// Runs when Alpine tears the component down (wire:navigate away).
|
||||
this.destroyed = true;
|
||||
this.alwaysScroll = false;
|
||||
this.cancelScrollLoop();
|
||||
if (this.scrollDebounce) {
|
||||
clearTimeout(this.scrollDebounce);
|
||||
this.scrollDebounce = null;
|
||||
}
|
||||
if (typeof this.morphUpdatedCleanup === 'function') {
|
||||
this.morphUpdatedCleanup();
|
||||
this.morphUpdatedCleanup = null;
|
||||
}
|
||||
if (typeof this.deploymentFinishedCleanup === 'function') {
|
||||
this.deploymentFinishedCleanup();
|
||||
this.deploymentFinishedCleanup = null;
|
||||
}
|
||||
}
|
||||
}">
|
||||
}" class="flex flex-1 min-h-0 flex-col overflow-hidden">
|
||||
<livewire:project.application.deployment-navbar
|
||||
:application_deployment_queue="$application_deployment_queue" />
|
||||
<div id="screen" :class="fullscreen ? 'fullscreen flex flex-col' : 'mt-4 relative'">
|
||||
<div id="screen" :class="fullscreen ? 'fullscreen flex flex-col' : 'mt-4 flex flex-1 min-h-0 flex-col overflow-hidden'">
|
||||
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
|
||||
class="flex flex-col w-full bg-white dark:text-white dark:bg-coolgray-100 dark:border-coolgray-300"
|
||||
:class="fullscreen ? 'h-full' : 'border border-dotted rounded-sm'">
|
||||
class="flex min-h-0 flex-col w-full overflow-hidden bg-white dark:text-white dark:bg-coolgray-100 dark:border-coolgray-300"
|
||||
:class="fullscreen ? 'h-full' : 'flex-1 border border-dotted rounded-sm'">
|
||||
<div
|
||||
class="flex flex-wrap items-center justify-between gap-2 px-4 py-2 border-b dark:border-coolgray-300 border-neutral-200 shrink-0">
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -210,12 +304,7 @@
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-1">
|
||||
<button
|
||||
x-on:click="
|
||||
$wire.copyLogs().then(logs => {
|
||||
navigator.clipboard.writeText(logs);
|
||||
Livewire.dispatch('success', ['Logs copied to clipboard.']);
|
||||
});
|
||||
"
|
||||
x-on:click="copyLogs()"
|
||||
title="Copy Logs"
|
||||
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
@@ -327,10 +416,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="logsContainer"
|
||||
class="flex flex-col overflow-y-auto p-2 px-4 min-h-4 scrollbar"
|
||||
:class="fullscreen ? 'flex-1' : 'max-h-[30rem]'">
|
||||
<div id="logs" class="flex flex-col font-mono">
|
||||
<div id="logsContainer" @scroll="handleScroll" @wheel="handleWheel"
|
||||
@touchstart="handleTouchStart" @touchmove="handleTouchMove" @keydown="handleKeyScroll" tabindex="0"
|
||||
class="flex min-h-40 flex-1 flex-col overflow-y-auto p-2 px-4 scrollbar">
|
||||
<div id="logs" class="flex flex-col font-logs">
|
||||
<div x-show="searchQuery.trim() && matchCount === 0"
|
||||
class="text-gray-500 dark:text-gray-400 py-2">
|
||||
No matches found.
|
||||
@@ -356,11 +445,11 @@
|
||||
])>{{ $lineContent }}</span>
|
||||
</div>
|
||||
@empty
|
||||
<span class="font-mono text-neutral-400 mb-2">No logs yet.</span>
|
||||
<span class="font-logs text-neutral-400 mb-2">No logs yet.</span>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,6 +12,9 @@
|
||||
<div>{{ $application->compose_parsing_version }}</div>
|
||||
@endif
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$application" />
|
||||
</x-modal-input>
|
||||
@if ($buildPack === 'dockercompose')
|
||||
<x-forms.button canGate="update" :canResource="$application" wire:target='initLoadingCompose'
|
||||
x-on:click="$wire.dispatch('loadCompose', false)">
|
||||
@@ -32,6 +35,7 @@
|
||||
<x-forms.select x-bind:disabled="shouldDisable()" wire:model.live="buildPack" label="Build Pack"
|
||||
required>
|
||||
<option value="nixpacks">Nixpacks</option>
|
||||
<option value="railpack">Railpack (Beta)</option>
|
||||
<option value="static">Static</option>
|
||||
<option value="dockerfile">Dockerfile</option>
|
||||
<option value="dockercompose">Docker Compose</option>
|
||||
@@ -60,7 +64,7 @@
|
||||
@if (!isDatabaseImage(data_get($service, 'image')))
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.input
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."
|
||||
label="Domains for {{ $serviceName }}"
|
||||
id="parsedServiceDomains.{{ str($serviceName)->replace('-', '_')->replace('.', '_') }}.domain"
|
||||
x-bind:disabled="shouldDisable()"></x-forms.input>
|
||||
@@ -114,7 +118,7 @@
|
||||
x-bind:disabled="!canUpdate" />
|
||||
@else
|
||||
<x-forms.input placeholder="https://coolify.io" wire:model="fqdn" label="Domains"
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."
|
||||
x-bind:disabled="!canUpdate" />
|
||||
@can('update', $application)
|
||||
<x-forms.button wire:click="getWildcardDomain">Generate Domain
|
||||
@@ -226,20 +230,24 @@
|
||||
id="customDockerRunOptions" label="Custom Docker Options" x-bind:disabled="!canUpdate" />
|
||||
@else
|
||||
@if ($application->could_set_build_commands())
|
||||
@if ($buildPack === 'nixpacks')
|
||||
@if ($buildPack === 'nixpacks' || $buildPack === 'railpack')
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
|
||||
<x-forms.input helper="If you modify this, you probably need to have a {{ $buildPack === 'railpack' ? 'railpack.json' : 'nixpacks.toml' }}"
|
||||
id="installCommand" label="Install Command" x-bind:disabled="!canUpdate" />
|
||||
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
|
||||
<x-forms.input helper="If you modify this, you probably need to have a {{ $buildPack === 'railpack' ? 'railpack.json' : 'nixpacks.toml' }}"
|
||||
id="buildCommand" label="Build Command" x-bind:disabled="!canUpdate" />
|
||||
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
|
||||
<x-forms.input helper="If you modify this, you probably need to have a {{ $buildPack === 'railpack' ? 'railpack.json' : 'nixpacks.toml' }}"
|
||||
id="startCommand" label="Start Command" x-bind:disabled="!canUpdate" />
|
||||
</div>
|
||||
<div class="pt-1 text-xs">Nixpacks will detect the required configuration
|
||||
automatically.
|
||||
@if ($buildPack === 'nixpacks')
|
||||
<div class="pt-1 text-xs">
|
||||
|
||||
<span class="font-medium">Nixpacks</span>
|
||||
will detect the required configuration automatically.
|
||||
<a class="underline" href="https://coolify.io/docs/applications/">Framework
|
||||
Specific Docs</a>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@endif
|
||||
@@ -276,7 +284,7 @@
|
||||
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($baseDirectory . $dockerComposeLocation, '/') }}</span>"
|
||||
x-model="composeLocation" @blur="normalizeComposeLocation()" />
|
||||
</div>
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox instantSave id="isPreserveRepositoryEnabled"
|
||||
label="Preserve Repository During Deployment"
|
||||
helper="Git repository (based on the base directory settings) will be copied to the deployment directory."
|
||||
@@ -382,7 +390,7 @@
|
||||
x-bind:disabled="!canUpdate" />
|
||||
|
||||
@if ($buildPack !== 'dockercompose')
|
||||
<div class="pt-2 w-96">
|
||||
<div class="pt-2 w-full sm:w-96">
|
||||
<x-forms.checkbox
|
||||
helper="Use a build server to build your application. You can configure your build server in the Server settings. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
|
||||
instantSave id="isBuildServerEnabled" label="Use a Build Server?"
|
||||
@@ -422,7 +430,7 @@
|
||||
monacoEditorLanguage="yaml" useMonacoEditor />
|
||||
</div>
|
||||
@endif
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox label="Escape special characters in labels?"
|
||||
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||
id="isContainerLabelEscapeEnabled" instantSave
|
||||
@@ -528,7 +536,7 @@
|
||||
|
||||
<h3 class="pt-8">HTTP Basic Authentication</h3>
|
||||
<div>
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave
|
||||
label="Enable" id="isHttpBasicAuthEnabled" x-bind:disabled="!canUpdate" />
|
||||
</div>
|
||||
@@ -550,7 +558,7 @@
|
||||
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"
|
||||
monacoEditorLanguage="ini" useMonacoEditor x-bind:disabled="!canUpdate"></x-forms.textarea>
|
||||
@endif
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox label="Readonly labels"
|
||||
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenerate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenerate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
|
||||
id="isContainerLabelReadonlyEnabled" instantSave
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<nav wire:poll.10000ms="checkStatus" class="pb-6">
|
||||
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
||||
<div class="navbar-main">
|
||||
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||
<a class="{{ request()->routeIs('project.application.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
<nav
|
||||
class="scrollbar flex min-h-10 w-full flex-nowrap items-center gap-6 overflow-x-scroll overflow-y-hidden pb-1 whitespace-nowrap md:w-auto md:overflow-visible">
|
||||
<a class="shrink-0 {{ request()->routeIs('project.application.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
href="{{ route('project.application.configuration', $parameters) }}">
|
||||
Configuration
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.application.deployment.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
<a class="shrink-0 {{ request()->routeIs('project.application.deployment.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
href="{{ route('project.application.deployment.index', $parameters) }}">
|
||||
Deployments
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.application.logs') ? 'dark:text-white' : '' }}"
|
||||
<a class="shrink-0 {{ request()->routeIs('project.application.logs') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('project.application.logs', $parameters) }}">
|
||||
<div class="flex items-center gap-1">
|
||||
Logs
|
||||
@@ -23,100 +24,160 @@
|
||||
</a>
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
@can('canAccessTerminal')
|
||||
<a class="{{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}"
|
||||
<a class="shrink-0 {{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('project.application.command', $parameters) }}">
|
||||
Terminal
|
||||
</a>
|
||||
@endcan
|
||||
@endif
|
||||
<x-applications.links :application="$application" />
|
||||
<div class="shrink-0">
|
||||
<x-applications.links :application="$application" />
|
||||
</div>
|
||||
</nav>
|
||||
<div class="flex flex-wrap gap-2 items-center">
|
||||
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
||||
<div>Please load a Compose file.</div>
|
||||
@else
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<div>
|
||||
<x-applications.advanced :application="$application" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@if (!str($application->status)->startsWith('exited'))
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400"
|
||||
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
|
||||
</path>
|
||||
<path d="M7.05 11.038v-3.988"></path>
|
||||
</svg>
|
||||
Redeploy
|
||||
</x-forms.button>
|
||||
<div class="md:hidden">
|
||||
<x-dropdown>
|
||||
<x-slot:title>
|
||||
Actions
|
||||
</x-slot>
|
||||
@if (!str($application->status)->startsWith('exited'))
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='deploy'>
|
||||
Redeploy
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='deploy'>
|
||||
Update Service
|
||||
</div>
|
||||
@else
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='restart'>
|
||||
Restart
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
||||
submitAction="stop" :checkboxes="$checkboxes" :actions="[
|
||||
'This application will be stopped.',
|
||||
'All non-persistent data of this application will be deleted.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:trigger>
|
||||
<div class="dropdown-item dropdown-item-touch text-error">
|
||||
Stop
|
||||
</div>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='deploy'>
|
||||
Deploy
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<x-forms.button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2">
|
||||
<path
|
||||
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Update Service
|
||||
</x-forms.button>
|
||||
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||
|
||||
@if ($application->status === 'running')
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='force_deploy_without_cache'>
|
||||
Force deploy (without cache)
|
||||
</div>
|
||||
@else
|
||||
<x-forms.button title="Restart without rebuilding" wire:click='restart'>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2">
|
||||
<path
|
||||
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Restart
|
||||
</x-forms.button>
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='deploy(true)'>
|
||||
Force deploy (without cache)
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
||||
submitAction="stop" :checkboxes="$checkboxes" :actions="[
|
||||
'This application will be stopped.',
|
||||
'All non-persistent data of this application will be deleted.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<x-forms.button wire:click='deploy'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
Deploy
|
||||
</x-forms.button>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
|
||||
<div class="hidden flex-wrap items-center gap-2 md:flex">
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<div>
|
||||
<x-applications.advanced :application="$application" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@if (!str($application->status)->startsWith('exited'))
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400"
|
||||
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
|
||||
</path>
|
||||
<path d="M7.05 11.038v-3.988"></path>
|
||||
</svg>
|
||||
Redeploy
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<x-forms.button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2">
|
||||
<path
|
||||
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Update Service
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button title="Restart without rebuilding" wire:click='restart'>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2">
|
||||
<path
|
||||
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Restart
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@endif
|
||||
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
||||
submitAction="stop" :checkboxes="$checkboxes" :actions="[
|
||||
'This application will be stopped.',
|
||||
'All non-persistent data of this application will be deleted.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<x-forms.button wire:click='deploy'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
Deploy
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -68,6 +68,20 @@
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if ($application->build_pack === 'dockerimage')
|
||||
<div class="flex flex-col gap-2 pt-4">
|
||||
<h3>Manual Preview Deployment</h3>
|
||||
<form wire:submit.prevent="addDockerImagePreview" class="flex flex-col gap-2 xl:flex-row xl:items-end">
|
||||
<x-forms.input id="manualPullRequestId" label="Pull Request Id"
|
||||
helper="Used as the preview identifier for naming, domains, logs, and cleanup." />
|
||||
<x-forms.input id="manualDockerTag" label="Docker Tag"
|
||||
helper="The image tag to deploy for this preview, for example pr_1234." />
|
||||
@can('deploy', $application)
|
||||
<x-forms.button type="submit">Deploy Preview</x-forms.button>
|
||||
@endcan
|
||||
</form>
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->previews->count() > 0)
|
||||
<h3 class="py-4">Deployments</h3>
|
||||
<div class="flex flex-wrap w-full gap-4">
|
||||
@@ -87,11 +101,13 @@
|
||||
<x-external-link />
|
||||
</a>
|
||||
@endif
|
||||
|
|
||||
<a target="_blank" href="{{ data_get($preview, 'pull_request_html_url') }}">Open
|
||||
PR on Git
|
||||
<x-external-link />
|
||||
</a>
|
||||
@if (filled(data_get($preview, 'pull_request_html_url')))
|
||||
|
|
||||
<a target="_blank" href="{{ data_get($preview, 'pull_request_html_url') }}">Open
|
||||
PR on Git
|
||||
<x-external-link />
|
||||
</a>
|
||||
@endif
|
||||
@if (count($parameters) > 0)
|
||||
|
|
||||
<a {{ wireNavigate() }}
|
||||
@@ -131,6 +147,10 @@
|
||||
<form wire:submit="save_preview('{{ $preview->id }}')" class="flex items-end gap-2 pt-4">
|
||||
<x-forms.input label="Domain" helper="One domain per preview."
|
||||
id="previewFqdns.{{ $previewName }}" canGate="update" :canResource="$application"></x-forms.input>
|
||||
@if ($application->build_pack === 'dockerimage')
|
||||
<x-forms.input label="Docker Tag" helper="The image tag used for this preview deployment."
|
||||
id="previewDockerTags.{{ $previewName }}" canGate="update" :canResource="$application"></x-forms.input>
|
||||
@endif
|
||||
@can('update', $application)
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
<x-forms.button wire:click="generate_preview('{{ $preview->id }}')">Generate
|
||||
@@ -157,7 +177,8 @@
|
||||
Force deploy (without
|
||||
cache)
|
||||
</x-forms.button>
|
||||
<x-forms.button wire:click="deploy({{ data_get($preview, 'pull_request_id') }})">
|
||||
<x-forms.button
|
||||
wire:click="deploy({{ data_get($preview, 'pull_request_id') }}, null, false, '{{ data_get($preview, 'docker_registry_image_tag') }}')">
|
||||
@if (data_get($preview, 'status') === 'exited')
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<span>
|
||||
@if ($application->server_status === false)
|
||||
<span title="One or more servers are unreachable or misconfigured.">
|
||||
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
|
||||
</svg>
|
||||
</span>
|
||||
@elseif ($application->additional_servers()->exists() && str($application->status)->contains('degraded'))
|
||||
<span title="Application is in degraded state across multiple servers.">
|
||||
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
|
||||
</svg>
|
||||
</span>
|
||||
@endif
|
||||
</span>
|
||||
@@ -2,6 +2,7 @@
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Swarm Configuration</h2>
|
||||
<x-deprecated-badge />
|
||||
@can('update', $application)
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
@@ -13,6 +14,9 @@
|
||||
</x-forms.button>
|
||||
@endcan
|
||||
</div>
|
||||
<x-callout type="warning" title="Deprecated" class="my-4">
|
||||
{{ config('deprecations.swarm') }}
|
||||
</x-callout>
|
||||
<div class="flex flex-col gap-2 py-4">
|
||||
<div class="flex flex-col items-end gap-2 xl:flex-row">
|
||||
<x-forms.input id="swarmReplicas" label="Replicas" required canGate="update" :canResource="$application" />
|
||||
|
||||
Reference in new issue
Block a user